将代码插入方法 - Java

Chr*_*row 7 java reflection code-generation aspectj

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

我有以下具有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;
      }
 }
Run Code Online (Sandbox Code Playgroud)

我也不确定我是否可以将方法名称的子集作为方法本身内部的字符串,如我将字段名作为字符串添加到更新字段集中的行所示: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;
  }
Run Code Online (Sandbox Code Playgroud)

}

Mic*_*ker 6

是的,你可以,一种方法是使用某种形式的字节码操作(例如javassist,ASM,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);
}
Run Code Online (Sandbox Code Playgroud)


Esp*_*pen 3

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

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

具有简单 Person 类和存根存储库的示例。有关更新哪些字段的所有信息均由名为 SetterAspect 的方面处理。它监视写入字段时更新哪些字段。

此示例中的其他建议是围绕存储库中的更新方法。这是检索从第一方面收集的数据。

人物类:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

存根存储库:

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);       
    }
}
Run Code Online (Sandbox Code Playgroud)

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

更新了存储库中 Person 的以下字段:[lastName,firstName]

这里需要注意的是, main() 方法调用了DtoRepository.update(T t)DtoRepository.update(T t, Set<String> 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());      
    }
}
Run Code Online (Sandbox Code Playgroud)

存储库方面:

@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");
        }
    }   
}
Run Code Online (Sandbox Code Playgroud)

最后,方面使用的两个辅助类:

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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());
    }
}
Run Code Online (Sandbox Code Playgroud)

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

我希望这能回答您的问题!