Существует ли способ автоматически ввести код в метод?
У меня есть следующее типичное поле с методом считывания и методом set, и я хотел бы ввести обозначенный код в метод установщика, который записывает, если поле было изменено также для вставки обозначенного "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;
}
}
Я также не уверен, могу ли я подмножество имени метода как строка из самого метода, как обозначено на строке, где я добавляю имя поля как строку в набор обновленных полей: 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;
}
}
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:
Обновлены следующие поля в Person в репозитории: [lastName, firstName]
Здесь важно отметить, что метод main () вызывает DtoRepository.update (T t)
, но DtoRepository.update (T t, Set
запускается из-за рекомендации в аспекте репозитория.
Аспект, который отслеживает все записи в закрытые поля в демонстрационном пакете:
@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
, вы могли бы избежать последнего аспекта.
Надеюсь, это ответ на ваш вопрос!
Да, можно, один из подходов - для использования какой-либо формы манипулирования байтовым кодом (например, 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);
}
Вы можете использовать динамические прокси-классы и получить событие до вызова setFirstName
и других методов set ...
, определить имя поля с помощью method.substring (3)
=> "FirstName", и поместите его в setFirstName
.