Вставить код в метод - Java - PullRequest
7 голосов
/ 13 июня 2010

Есть ли способ автоматической вставки кода в метод?

У меня есть следующее типичное поле с геттером и сеттером, и я хотел бы вставить указанный код в метод сеттера, который записывает, если полетакже был изменен для вставки указанного поля «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");.И я не уверен, как вставить поля в класс, где я добавляю логическое поле, которое отслеживает, было ли поле изменено или нет ранее (для эффективности, чтобы избежать манипулирования множеством): boolean isFirstNameChanged = false;

Кажется, наиболее очевидным ответом на это было бы использование шаблонов кода внутри затмения, но я беспокоюсь о необходимости вернуться и изменить код позже.

Редактировать ::::::::::

Я должен был использовать этот более простой код вместо приведенного выше примера.Все, что он делает, это добавляет имя поля в виде строки к набору.

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

}

Ответы [ 3 ]

6 голосов
/ 13 июня 2010

Да, вы можете, один из подходов состоит в том, чтобы использовать некоторую форму манипулирования байтовым кодом (например, 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);
}
3 голосов
/ 13 июня 2010

С AspectJ вы можете изменять методы и поля с советами.

Мой пример написан с синтаксисом @AspectJ, который изменяет код во время компиляции или загрузки. Если вы хотите внести изменения во время выполнения, вы можете использовать Spring AOP, который также поддерживает этот синтаксис @AspectJ.

Пример с простым классом 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);       
    }
}

Выходные данные для выполнения метода main() в классе Person с AspectJ:

Обновлены следующие поля в Персоне в репозитории: [lastName, ПгвЬЫате]

Здесь важно отметить, что метод 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 реализовали интерфейс, который возвращает Set<String>, вы могли бы избежать последнего аспекта.

Надеюсь, это ответило на ваш вопрос!

1 голос
/ 13 июня 2010

Вы можете использовать Динамические прокси-классы и получить событие до вызова setFirstName и других методов set..., определить имя поля с помощью method.substring(3) => «FirstName» и вставить его в setFirstName.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...