AOP pointcut для аргумента, который имеет поле с аннотацией? - PullRequest
0 голосов
/ 23 июня 2019

Проблема: я хочу вызвать метод вручную, используя АОП, когда вызывается определенный метод с аргументом определенного типа, который имеет поле с конкретной аннотацией.

Прямо сейчас я могу сделать это вдва разных способа: 1. «вызвать метод вручную, используя AOP, когда определенный метод вызывается с аргументом определенного типа».Затем извлеките аннотированное поле с помощью отражения от точки соединения.

2.или аннотируйте сам тип с именами полей в качестве значения аннотации

Но помимо этого, как я должен сразу поместить их в выражение pointcutпроверить наличие аннотированного поля?

Пример:

class  A {
}

class B extends A{
  someField;
}

class C extends A{
  @CustomAnnotation
  someField;
}

существуют перегруженные методы, в которых я хочу выполнить действие «до»: например:

  public void doSomething(A a);
  public void doSomething(X x);

С помощью следующего pointcut я могу поймать действие, когда тип параметра A:

    @Pointcut("execution(* somePackage.doSomething((A)))")
    public void customPointCut() {
    }

    @Before("customPointCut()")
    public void customAction(JoinPoint joinPoint) throws Throwable{   
              //examining fields with reflection whether they are annotated or not
              //action
    }

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

"проверка полей с отражением независимо от того, аннотированы они или нет"

Так только классБудет захвачено C.
Примерно так: @Pointcut("execution(* somePackage.doSomething((A.fieldhas(@CustomAnnotation))))")

edit2: для части требований: мне нужно перезаписать значение (это приватное поле, но с открытым сеттером).

1 Ответ

1 голос
/ 27 июня 2019

Хорошо, даже после нескольких запросов я не получил от вас четкого ответа, когда и где вы хотите манипулировать значениями полей. Итак, я покажу вам тремя различными способами. Все они связаны с использованием полноценного AspectJ , и я также буду использовать собственный синтаксис, потому что первый способ, которым я собираюсь показать вам, не работает в аннотациях стиль синтаксиса. Вам необходимо скомпилировать аспекты с помощью компилятора AspectJ. Вносите ли вы это в код своего приложения во время компиляции или через время загрузки - решать вам. Мое решение полностью работает без Spring, но если вы являетесь пользователем Spring, вы можете объединить его с Spring и даже смешать с Spring AOP. Пожалуйста, прочитайте руководство Spring для дальнейших инструкций.

Я показываю вам в моем примере кода:

  1. Объявление между типами (ITD): это наиболее сложный способ, в котором используется обозначение точки hasfield(). Чтобы использовать его, необходимо вызвать компилятор AspectJ со специальным флагом -XhasMember. В Eclipse с установленным AJDT параметр называется «Имеет член» в настройках проекта в «Компилятор AspectJ», «Другое». Что мы делаем здесь:

    • заставить все классы с аннотированными полями реализовывать интерфейс маркера HasMyAnnotationField
    • всякий раз, когда вызывается метод с типом параметра, реализующим интерфейс, что-то выводится на консоль, и, необязательно, значение поля обрабатывается с помощью отражения, возможно, аналогично вашему собственному решению.
  2. Управляйте значением поля во время доступа к записи с помощью set() совета. Это постоянно изменяет значение поля и не требует никаких ITD с интерфейсом маркера, специальными флагами компилятора и отражением, подобным решению 1.

  3. Прозрачно манипулирует значением, возвращаемым из поля для чтения с помощью 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###~]

Как видите,

  1. отражающий доступ окружает значение поля на # каждый раз, когда вызывается один из методов doSomethingSpecial(..) или doSomethingVerySpecial(..) - всего 3x из-за цикла for, в результате чего ### предварительно и суффиксы в конце.

  2. доступ к записи в поле происходит только один раз при создании объекта и постоянно изменяет строковое значение на верхний регистр.

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

Также обратите внимание, что вы можете определить, хотите ли вы получить доступ ко всем аннотированным полям, как в hasfield(@MyAnnotation * *), или, возможно, ограничиться определенным типом, как в set(@MyAnnotation String *) или get(@MyAnnotation String *).

Для получения дополнительной информации, например, о ITD через declare parents и более экзотических типах точек, используемых в моем примере кода, пожалуйста, обратитесь к документации AspectJ.

Обновление: После того, как я разделил свой монолитный аспект на 3 отдельных аспекта, я могу сказать, что если вам не нужно первое решение, использующее hasfield(), но одно из двух других, вероятно, вы можете использовать @ Стиль аннотации AspectJ для написания аспектов, скомпилируйте их с помощью обычного Java-компилятора и дайте ткачу времени загрузки позаботиться о завершении аспекта и включении его в код приложения. Ограничение собственного синтаксиса применимо только к первому аспекту.

...