Вставка кода в метод - Java

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

У меня есть следующее типичное поле с геттером и сеттером, и я хотел бы вставить указанный код в метод установки, который также записывает, было ли поле изменено, чтобы вставить указанное поле «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;

Кажется, что наиболее очевидным ответом на это было бы использование шаблонов кода внутри 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;
  }

}


person Chris Dutrow    schedule 13.06.2010    source источник
comment
Вы имеете в виду динамически во время выполнения?   -  person Yann Ramin    schedule 13.06.2010
comment
Вероятно, во время выполнения, но это может произойти даже во время компиляции. Я просто не хочу кодировать его таким образом, чтобы мне пришлось вручную все переделывать, если мне нужно изменить способ его реализации.   -  person Chris Dutrow    schedule 13.06.2010
comment
(для эффективности, чтобы избежать необходимости манипулировать набором) Это будет последняя из ваших проблем с эффективностью. Мне кажется, вы хотите как-то изменить метод во время выполнения? Я был бы там 100 лучших решений для вашей проблемы.   -  person Ed S.    schedule 13.06.2010
comment
@Ed Swangren - Что было бы лучшим решением? У меня сложилось впечатление, что набор должен перебирать символы в строке, чтобы хэшировать ее, а затем выполнять вставку где-то около O (logN)?   -  person Chris Dutrow    schedule 13.06.2010
comment
У вас есть миллионы атрибутов, таких как firstName, для оптимизации метода add набора?   -  person True Soft    schedule 13.06.2010
comment
@True Soft - Нет, не знаю. Но была некоторая озабоченность по поводу того, что нужно делать много манипуляций с объектами для отчетов и чего-то еще. Я не должен был включать оптимизацию в код примера. Суть в том, что мне нужно отметить, какие поля были обновлены, чтобы хранилище данных знало, какие свойства следует перезаписать.   -  person Chris Dutrow    schedule 13.06.2010


Ответы (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);
    }
}

Репозиторий заглушки:

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:

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

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

person Espen    schedule 13.06.2010

Да, вы можете, один из подходов заключается в использовании некоторой формы манипуляции с байтовым кодом (например, javassist, ASM, BCEL) или библиотека АОП более высокого уровня размещается поверх одного из эти инструменты, т.е. 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);
}
person Michael Barker    schedule 13.06.2010

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

person True Soft    schedule 13.06.2010