7

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

我有以下带有 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;
  }

}

4

3 回答 3

6

是的,你可以,一种方法是使用某种形式的字节码操作(例如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);
}
于 2010-06-13T06:21:37.870 回答
3

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

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

一个简单的 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);
    }
}

存根存储库:

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);       
    }
}

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());      
    }
}

存储库方面:

@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 都实现了一个返回 a 的接口Set<String>,那么您可以避免最后一个方面。

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

于 2010-06-13T12:23:53.640 回答
1

可以使用动态代理类,在调用ofsetFirstName等方法之前获取事件set...,用=>“FirstName”确定字段名method.substring(3),放入setFirstName

于 2010-06-13T06:17:52.093 回答