Хорошо, даже после нескольких запросов я не получил от вас четкого ответа, когда и где вы хотите манипулировать значениями полей. Итак, я покажу вам тремя различными способами. Все они связаны с использованием полноценного AspectJ , и я также буду использовать собственный синтаксис, потому что первый способ, которым я собираюсь показать вам, не работает в аннотациях стиль синтаксиса. Вам необходимо скомпилировать аспекты с помощью компилятора AspectJ. Вносите ли вы это в код своего приложения во время компиляции или через время загрузки - решать вам. Мое решение полностью работает без Spring, но если вы являетесь пользователем Spring, вы можете объединить его с Spring и даже смешать с Spring AOP. Пожалуйста, прочитайте руководство Spring для дальнейших инструкций.
Я показываю вам в моем примере кода:
Объявление между типами (ITD): это наиболее сложный способ, в котором используется обозначение точки hasfield()
. Чтобы использовать его, необходимо вызвать компилятор AspectJ со специальным флагом -XhasMember
. В Eclipse с установленным AJDT параметр называется «Имеет член» в настройках проекта в «Компилятор AspectJ», «Другое». Что мы делаем здесь:
- заставить все классы с аннотированными полями реализовывать интерфейс маркера
HasMyAnnotationField
- всякий раз, когда вызывается метод с типом параметра, реализующим интерфейс, что-то выводится на консоль, и, необязательно, значение поля обрабатывается с помощью отражения, возможно, аналогично вашему собственному решению.
Управляйте значением поля во время доступа к записи с помощью set()
совета. Это постоянно изменяет значение поля и не требует никаких ITD с интерфейсом маркера, специальными флагами компилятора и отражением, подобным решению 1.
Прозрачно манипулирует значением, возвращаемым из поля для чтения с помощью get()
advice. Само поле остается без изменений.
Возможно, вы хотите № 2 или № 3, я показываю решение № 1 для полноты картины.
Достаточно слов, вот полный MCVE :
Примечание к полю:
package de.scrum_master.app;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
@Retention(RUNTIME)
@Target(FIELD)
public @interface MyAnnotation {}
Пример класса с использованием аннотации поля:
package de.scrum_master.app;
public class MyClass {
private int id;
@MyAnnotation
private String name;
public MyClass(int id, String name) {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "MyClass [id=" + id + ", name=" + name + "]";
}
}
Приложение драйвера:
package de.scrum_master.app;
public class Application {
public void doSomething() {}
public void doSomethingElse(int i, String string) {}
public void doSomethingSpecial(int i, MyClass myClass) {
System.out.println(" " + myClass);
}
public int doSomethingVerySpecial(MyClass myClass) {
System.out.println(" " + myClass);
return 0;
}
public static void main(String[] args) {
Application application = new Application();
MyClass myClass1 = new MyClass(11, "John Doe");
MyClass myClass2 = new MyClass(11, "Jane Doe");
for (int i = 0; i < 3; i++) {
application.doSomething();
application.doSomethingElse(7, "foo");
application.doSomethingSpecial(3, myClass1);
application.doSomethingVerySpecial(myClass2);
}
}
}
Журнал консоли без аспектов:
MyClass [id=11, name=John Doe]
MyClass [id=11, name=Jane Doe]
MyClass [id=11, name=John Doe]
MyClass [id=11, name=Jane Doe]
MyClass [id=11, name=John Doe]
MyClass [id=11, name=Jane Doe]
Здесь нет сюрпризов. Мы создали два MyClass
объекта и вызвали некоторые Application
методы, только два из которых на самом деле имеют MyClass
параметры (то есть типы параметров, по крайней мере, с одним полем, аннотированным MyAnnotation
). Мы ожидаем, что что-то случится, когда аспекты вступят в действие. Но прежде чем мы напишем аспекты, нам сначала нужно что-то еще:
Маркерный интерфейс для классов с @MyAnnotation
полями:
package de.scrum_master.app;
public interface HasMyAnnotationField {}
А вот наши аспекты:
Аспекты, показывающие 3 способа манипулирования значениями поля:
package de.scrum_master.aspect;
import java.lang.reflect.Field;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.SoftException;
import org.aspectj.lang.reflect.MethodSignature;
import de.scrum_master.app.HasMyAnnotationField;
import de.scrum_master.app.MyAnnotation;
public aspect ITDAndReflectionAspect {
// Make classes with @MyAnnotation annotated fields implement marker interface
declare parents : hasfield(@MyAnnotation * *) implements HasMyAnnotationField;
// Intercept methods with parameters implementing marker interface
before() : execution(* *(.., HasMyAnnotationField+, ..)) {
System.out.println(thisJoinPoint);
manipulateAnnotatedFields(thisJoinPoint);
}
// Reflectively manipulate @MyAnnotation fields of type String
private void manipulateAnnotatedFields(JoinPoint thisJoinPoint) {
Object[] methodArgs = thisJoinPoint.getArgs();
MethodSignature signature = (MethodSignature) thisJoinPoint.getSignature();
Class<?>[] parameterTypes = signature.getParameterTypes();
int argIndex = 0;
for (Class<?> parameterType : parameterTypes) {
Object methodArg = methodArgs[argIndex++];
for (Field field : parameterType.getDeclaredFields()) {
field.setAccessible(true);
if (field.getAnnotation(MyAnnotation.class) == null)
continue;
// If using 'hasfield(@MyAnnotation String *)' we can skip this type check
if (field.getType().equals(String.class)) {
try {
field.set(methodArg, "#" + ((String) field.get(methodArg)) + "#");
} catch (IllegalArgumentException | IllegalAccessException e) {
throw new SoftException(e);
}
}
}
}
}
}
package de.scrum_master.aspect;
import de.scrum_master.app.MyAnnotation;
public aspect SetterInterceptor {
// Persistently change field value during write access
Object around(String string) : set(@MyAnnotation String *) && args(string) {
System.out.println(thisJoinPoint);
return proceed(string.toUpperCase());
}
}
package de.scrum_master.aspect;
import de.scrum_master.app.MyAnnotation;
public aspect GetterInterceptor {
// Transparently return changed value during read access
Object around() : get(@MyAnnotation String *) {
System.out.println(thisJoinPoint);
return "~" + proceed() + "~";
}
}
Журнал консоли со всеми 3 активированными аспектами:
set(String de.scrum_master.app.MyClass.name)
set(String de.scrum_master.app.MyClass.name)
execution(void de.scrum_master.app.Application.doSomethingSpecial(int, MyClass))
get(String de.scrum_master.app.MyClass.name)
MyClass [id=11, name=~#JOHN DOE#~]
execution(int de.scrum_master.app.Application.doSomethingVerySpecial(MyClass))
get(String de.scrum_master.app.MyClass.name)
MyClass [id=11, name=~#JANE DOE#~]
execution(void de.scrum_master.app.Application.doSomethingSpecial(int, MyClass))
get(String de.scrum_master.app.MyClass.name)
MyClass [id=11, name=~##JOHN DOE##~]
execution(int de.scrum_master.app.Application.doSomethingVerySpecial(MyClass))
get(String de.scrum_master.app.MyClass.name)
MyClass [id=11, name=~##JANE DOE##~]
execution(void de.scrum_master.app.Application.doSomethingSpecial(int, MyClass))
get(String de.scrum_master.app.MyClass.name)
MyClass [id=11, name=~###JOHN DOE###~]
execution(int de.scrum_master.app.Application.doSomethingVerySpecial(MyClass))
get(String de.scrum_master.app.MyClass.name)
MyClass [id=11, name=~###JANE DOE###~]
Как видите,
отражающий доступ окружает значение поля на #
каждый раз, когда вызывается один из методов doSomethingSpecial(..)
или doSomethingVerySpecial(..)
- всего 3x из-за цикла for
, в результате чего ###
предварительно и суффиксы в конце.
доступ к записи в поле происходит только один раз при создании объекта и постоянно изменяет строковое значение на верхний регистр.
поле для чтения в режиме чтения прозрачно оборачивает сохраненное значение в ~
символы, которые не сохраняются, в противном случае они будут больше походить на символы #
из метода 1, поскольку доступ для чтения происходит несколько раз.
Также обратите внимание, что вы можете определить, хотите ли вы получить доступ ко всем аннотированным полям, как в hasfield(@MyAnnotation * *)
, или, возможно, ограничиться определенным типом, как в set(@MyAnnotation String *)
или get(@MyAnnotation String *)
.
Для получения дополнительной информации, например, о ITD через declare parents
и более экзотических типах точек, используемых в моем примере кода, пожалуйста, обратитесь к документации AspectJ.
Обновление: После того, как я разделил свой монолитный аспект на 3 отдельных аспекта, я могу сказать, что если вам не нужно первое решение, использующее hasfield()
, но одно из двух других, вероятно, вы можете использовать @ Стиль аннотации AspectJ для написания аспектов, скомпилируйте их с помощью обычного Java-компилятора и дайте ткачу времени загрузки позаботиться о завершении аспекта и включении его в код приложения. Ограничение собственного синтаксиса применимо только к первому аспекту.