【问题标题】:Insert code into a method - Java将代码插入方法 - Java
【发布时间】:2011-03-03 03:42:07
【问题描述】:

有没有办法在方法中自动插入代码?

我有以下带有 getter 和 setter 的典型字段,我想将指示的代码插入到记录该字段是否被修改的 setter 方法中,以插入指示的“isFirstNameModified”字段以跟踪该字段是否是否修改过。

 public class Person {

      Set<String> updatedFields = new LinkedHashSet<String>();

      String firstName;
      public String getFirstName(){
           return firstName;
      }

      boolean isFirstNameChanged = false;           // This code is inserted later
      public void setFirstName(String firstName){       
           if( !isFirstNameChanged ){               // This code is inserted later
                isFirstNameChanged = true;          // This code is inserted later
                updatedFields.add("firstName");     // This code is inserted later
           }                                        // This code is inserted later
           this.firstName = firstName;
      }
 }

我也不确定是否可以将方法名称的子集作为方法本身内部的字符串,如我将 fieldName 作为字符串添加到更新字段集的行中所示:updatedFields.add("firstName");。而且我不确定如何将字段插入到一个类中,在该类中我添加了一个布尔字段来跟踪该字段是否已被修改(为了提高效率以防止不得不操作 Set):boolean isFirstNameChanged = false;

似乎最明显的答案是在 Eclipse 中使用代码模板,但我担心以后必须返回并更改代码。

编辑:::::::::

我应该使用这个更简单的代码而不是上面的示例。它所做的只是将字段名称作为字符串添加到集合中。

 public class Person {

  Set<String> updatedFields = new LinkedHashSet<String>();

  String firstName;
  public String getFirstName(){
       return firstName;
  }
  public void setFirstName(String firstName){       
       updatedFields.add("firstName");        // This code is inserted later
       this.firstName = firstName;
  }

}

【问题讨论】:

  • 你的意思是在运行时动态吗?
  • 可能在运行时发生,但它甚至可能在编译时发生。我只是不想以这样一种方式编码它,如果我想改变它的实现方式,我必须手动重新做所有事情
  • "(为了提高效率以防止不得不操纵 Set)" 这将是您对效率的最后一个关注点。听起来你想在运行时以某种方式修改方法?我希望有 100 个更好的解决方案来解决您的问题。
  • @Ed Swangren - 什么是更好的解决方案?我的印象是该集合必须遍历字符串中的字符才能对其进行哈希处理,然后在 O(logN) 附近的某处进行插入?
  • 你有像firstName这样的百万属性来优化集合的add方法吗?

标签: java reflection code-generation aspectj


【解决方案1】:

是的,您可以,一种方法是使用某种形式的字节码操作(例如javassistASM、BCEL)或位于这些工具之一之上的更高级别的 AOP 库,例如AspectJ,JBoss AOP。

注意:大多数 JDO 库都这样做是为了处理持久性。

这是一个使用 javassist 的示例:

public class Person {

    private String firstName;

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }
}

public static void rewritePersonClass() throws NotFoundException, CannotCompileException {
    ClassPool pool = ClassPool.getDefault();
    CtClass ctPerson = pool.get("Person");
    CtClass ctSet = pool.get("java.util.LinkedHashSet");

    CtField setField = new CtField(ctSet, "updatedFields", ctPerson);
    ctPerson.addField(setField, "new java.util.LinkedHashSet();");

    CtMethod method = ctPerson.getDeclaredMethod("setFirstName");
    method.insertBefore("updatedFields.add(\"firstName\");");

    ctPerson.toClass();
}


public static void main(String[] args) throws Exception {
    rewritePersonClass();

    Person p = new Person();
    p.setFirstName("foo");

    Field field = Person.class.getDeclaredField("updatedFields");
    field.setAccessible(true);
    Set<?> s = (Set<?>) field.get(p);

    System.out.println(s);
}

【讨论】:

    【解决方案2】:

    使用 AspectJ,您可以使用建议修改方法和字段。

    我的示例是用@AspectJ 语法编写的,它在编译时或加载时修改代码。如果你想在运行时修改,你可以使用同样支持@AspectJ语法的Spring AOP。

    一个简单的 Person 类和一个存根存储库的示例。所有关于哪些字段被更新的信息都由一个称为 SetterAspect 的方面处理。它监视在写入字段时更新了哪些字段。

    本示例中的另一个建议是关于存储库中的更新方法。这是检索从第一方面收集的数据。

    Person 类:

    public class Person {
    
        private String firstName;
    
        private String lastName;
    
        public String getFirstName() {
            return firstName;
        }
    
        public void setFirstName(String firstName) {
            this.firstName = firstName;
        }
    
        public String getLastName() {
            return lastName;
        }
    
        public void setLastName(String lastName) {
            this.lastName = lastName;
        }   
    
        public static void main(String[] args) {
            Person person = new Person();
            person.setFirstName("James");
            person.lastName = "Jameson";
    
            DtoRepository<Person> personRepository = new DtoRepository<Person>();
            personRepository.update(person);
        }
    }
    

    存根存储库:

    public class DtoRepository<T> {
    
        public void update(T t) {
            System.out.println(t.getClass().getSimpleName() + " updated..");
        }
    
        public void updatePerson(T t, Set<String> updatedFields) {
            System.out.print("Updated the following fields on " +
                t.getClass().getSimpleName() + " in the repository: "
                + updatedFields);       
        }
    }
    

    使用 AspectJ 在 Person 类中执行 main() 方法的输出:

    更新了 Person 的以下字段 在存储库中:[姓氏, 名字]

    这里需要注意的重要一点是,main() 方法调用了 DtoRepository.update(T t),但由于存储库方面的环绕通知,DtoRepository.update(T t, Set&lt;String&gt; updatedFields) 被执行。

    监控所有写入演示包中私有字段的方面:

    @Aspect
    public class SetterAspect {
    
        private UpdatableDtoManager updatableDtoManager = 
            UpdatableDtoManager.INSTANCE;
    
        @Pointcut("set(private * demo.*.*)")
        public void setterMethod() {}
    
        @AfterReturning("setterMethod()")
        public void afterSetMethod(JoinPoint joinPoint) {
            String fieldName = joinPoint.getSignature().getName();
            updatableDtoManager.updateObjectWithUpdatedField(
                    fieldName, joinPoint.getTarget());      
        }
    }
    

    存储库方面:

    @Aspect
    public class UpdatableDtoRepositoryAspect {
    
        private UpdatableDtoManager updatableDtoManager = 
            UpdatableDtoManager.INSTANCE;
    
        @Pointcut("execution(void demo.DtoRepository.update(*)) " +
                "&& args(object)")
        public void updateMethodInRepository(Object object) {}
    
        @Around("updateMethodInRepository(object)")
        public void aroundUpdateMethodInRepository(
                ProceedingJoinPoint joinPoint, Object object) {
    
            Set<String> updatedFields = 
                updatableDtoManager.getUpdatedFieldsForObject(object);
    
            if (updatedFields.size() > 0) {
                ((DtoRepository<Object>)joinPoint.getTarget()).
                    updatePerson(object, updatedFields);
            } else {
    
                // Returns without calling the repository.
                System.out.println("Nothing to update");
            }
        }   
    }
    

    最后是切面使用的两个辅助类:

    public enum UpdatableDtoManager {
    
        INSTANCE;
    
        private Map<Object, UpdatedObject> updatedObjects = 
            new HashMap<Object, UpdatedObject>();
    
        public void updateObjectWithUpdatedField(
                String fieldName, Object object) {
            if (!updatedObjects.containsKey(object)) {
                updatedObjects.put(object, new UpdatedObject());
            }
    
            UpdatedObject updatedObject = updatedObjects.get(object);
            if (!updatedObject.containsField(fieldName)) {
                updatedObject.add(fieldName);
            }
        }
    
        public Set<String> getUpdatedFieldsForObject(Object object) {
            UpdatedObject updatedObject = updatedObjects.get(object);
    
            final Set<String> updatedFields;
            if (updatedObject != null) {
                updatedFields = updatedObject.getUpdatedFields();
            } else {
                updatedFields = Collections.emptySet();
            }
    
            return updatedFields;
        }
    }
    

    public class UpdatedObject {
    
        private Map<String, Object> updatedFields = 
            new HashMap<String, Object>();
    
        public boolean containsField(String fieldName) {
            return updatedFields.containsKey(fieldName);
        }
    
        public void add(String fieldName) {
            updatedFields.put(fieldName, fieldName);        
        }
    
        public Set<String> getUpdatedFields() {
            return Collections.unmodifiableSet(
                    updatedFields.keySet());
        }
    }
    

    我的示例使用方面执行所有更新逻辑。如果所有 DTO 都实现了一个返回 Set&lt;String&gt; 的接口,那么您可以避免最后一个方面。

    我希望这回答了你的问题!

    【讨论】:

      【解决方案3】:

      可以使用Dynamic Proxy Classes,在调用setFirstName等方法set...之前获取事件,用method.substring(3)=>“FirstName”确定字段名,并放入setFirstName

      【讨论】:

        猜你喜欢
        • 2017-12-25
        • 1970-01-01
        • 1970-01-01
        • 2022-01-17
        • 2018-08-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多