Spring AOP: есть ли способ заставить @target работать с косвенными аннотациями?

У меня есть аннотация:

@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
public @interface MyAnnotation {
}

Я аннотирую с его помощью контроллеры Spring MVC:

@MyAnnotation
public class TestController { ... }

Затем я добавляю совет, в котором есть следующее:

@Pointcut("@target(MyAnnotation)")
public void annotatedWithMyAnnotation() {}

@Around("annotatedWithMyAnnotation()")
public Object executeController(ProceedingJoinPoint point) throws Throwable { ... }

Метод совета успешно запущен.

Теперь у меня есть несколько контроллеров, использующих одни и те же аннотации, и я хочу использовать стереотипную аннотацию для их группировки.

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@MyAnnotation
... other annotations
public @interface StereotypeAnnotation {
}

А затем я аннотирую свои контроллеры с помощью @StereotypeAnnotation:

@StereotypeAnnotation
public class TestController { ... }

Контроллеры больше не содержат @MyAnnotation напрямую.

Проблема заключается в том, что в таком случае pointcut @target перестает соответствовать моим контроллерам, и они не рекомендуются.

Есть ли способ определить pointcut, который соответствовал бы контроллерам, имеющим такие косвенные аннотации?

Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
0
467
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Я воссоздал ситуацию с чистым AspectJ, потому что мне не нравится Spring AOP. Вот почему я добавил дополнительный execution(* *(..)) && перед pointcut для совета, чтобы избежать сопоставления с другими точками соединения, недоступными в Spring AOP, такими как call(). Вы можете удалить его в Spring AOP, если хотите.

Хорошо, давайте создадим эту ситуацию, как вы ее описали:

package de.scrum_master.app;

import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.ANNOTATION_TYPE })
public @interface MyAnnotation {}
package de.scrum_master.app;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ ElementType.TYPE, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@MyAnnotation
public @interface StereotypeAnnotation {}
package de.scrum_master.app;

@MyAnnotation
public class TestController {
  public void doSomething() {
    System.out.println("Doing something");
  }
}
package de.scrum_master.app;

@StereotypeAnnotation
public class AnotherController {
  public void doSomething() {
    System.out.println("Doing yet another something");
  }
}

Это наше приложение с драйвером на чистом Java (без Spring):

package de.scrum_master.app;

public class Application {
  public static void main(String[] args) {
    new TestController().doSomething();
    new AnotherController().doSomething();
  }
}

А это аспект:

package de.scrum_master.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class MetaAnnotationAspect {
  @Pointcut(
    "@target(de.scrum_master.app.MyAnnotation) || " +
    "@target(de.scrum_master.app.StereotypeAnnotation)"
  )
  public void solutionA() {}

  @Around("execution(* *(..)) && solutionA()")
  public Object executeController(ProceedingJoinPoint point) throws Throwable {
    System.out.println(point);
    return point.proceed();
  }
}

Вывод журнала будет:

execution(void de.scrum_master.app.TestController.doSomething())
Doing something
execution(void de.scrum_master.app.AnotherController.doSomething())
Doing yet another something

Все идет нормально. Но что, если мы добавим еще один уровень вложенности?

package de.scrum_master.app;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@StereotypeAnnotation
public @interface SubStereotypeAnnotation {}
package de.scrum_master.app;

@SubStereotypeAnnotation
public class YetAnotherController {
  public void doSomething() {
    System.out.println("Doing another something");
  }
}
package de.scrum_master.app;

public class Application {
  public static void main(String[] args) {
    new TestController().doSomething();
    new AnotherController().doSomething();
    new YetAnotherController().doSomething();
  }
}

Тогда pointcut больше не будет соответствовать вложенной аннотации мета / стереотипа:

execution(void de.scrum_master.app.TestController.doSomething())
Doing something
execution(void de.scrum_master.app.AnotherController.doSomething())
Doing yet another something
Doing another something

Нам пришлось бы явно добавить || @target(de.scrum_master.app.StereotypeAnnotation) в pointcut, т.е. нам нужно было бы знать все имена классов аннотаций в иерархии. Есть способ преодолеть это, используя специальный синтаксис для обозначения pointcut within(), см. Также мой другой ответ здесь:

package de.scrum_master.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class MetaAnnotationAspect {
  @Pointcut(
    "within(@de.scrum_master.app.MyAnnotation *) || " +
    "within(@(@de.scrum_master.app.MyAnnotation *) *) || " +
    "within(@(@(@de.scrum_master.app.MyAnnotation *) *) *)"
  )
  public void solutionB() {}

  @Around("execution(* *(..)) && solutionB()")
  public Object executeController(ProceedingJoinPoint point) throws Throwable {
    System.out.println(point);
    return point.proceed();
  }
}

Журнал консоли изменится на:

execution(void de.scrum_master.app.TestController.doSomething())
Doing something
execution(void de.scrum_master.app.AnotherController.doSomething())
Doing yet another something
execution(void de.scrum_master.app.YetAnotherController.doSomething())
Doing another something

Видеть? Нам нужно знать только один класс аннотаций, а именно MyAnnotation, чтобы покрыть два уровня вложенности метааннотаций. Было бы просто добавить больше уровней. Я признаю, что такое вложение аннотаций кажется довольно надуманным, я просто хотел объяснить вам, какие варианты у вас есть.

Большое спасибо!

Roman Puchkovskiy 15.12.2018 13:17

Другие вопросы по теме