Как перехватывать мета-аннотации (аннотированные аннотации) в Spring AOP

Предположим, я хочу найти все классы, помеченные @Controller, я бы создал этот pointcut:

    @Pointcut("within(@org.springframework.stereotype.Controller *)")
    public void controllerPointcut() {}

Но эти контроллеры, помеченные @RestController, не могут быть найдены. Поскольку сам RestController аннотируется @Controller.

Есть идеи, как найти классы, аннотированные с помощью @Controller или @RestController, без необходимости создавать два Pointcuts?


===== edit ==== Мои настоящие намерения заключаются в следующем:

родительская аннотация:

public @interface ParentAnnotation {}

дочерняя аннотация (аннотированная @ParentAnnotation):

@ParentAnnotation 
public @interface ChildAnnotation {}

класс А:

@ParentAnnotation 
public class MyClassA {}

класс B:

@ChildAnnotation 
public class MyClassB {}

Теперь я хочу найти MyClassA и MyClassB через @ParentAnnotation. Нет вопросов о поиске MyClassA, но MyClassB косвенно аннотируется @ParentAnnotation, есть ли общий способ справиться с такой ситуацией?


person maojf    schedule 31.01.2018    source источник


Ответы (1)


Как насчет этого?

@Pointcut(
  "within(@org.springframework.stereotype.Controller *) || " + 
  "within(@org.springframework.web.bind.annotation.RestController *)" + 
)

Или немного короче, но, возможно, слишком нечетко, если в пакетах Springs есть другие классы с совпадающими именами (я не проверял):

@Pointcut("within(@(org.springframework..*Controller) *)")

Обновление: Что касается вашего реального вопроса, я понял его теперь после вашего редактирования. Это тоже возможно, но синтаксически сложно. Позвольте мне переименовать ваши аннотации в MetaAnnotation и MyAnnotation, хорошо? Поскольку они на самом деле не являются родительскими и дочерними друг для друга, в смысле ООП нет наследования, а только вложение.

Аннотации:

Убедитесь, что аннотации действительно имеют область выполнения. Я не видел этого в вашем коде.

package de.scrum_master.app;

import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

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

@Retention(RUNTIME)
@Target({ TYPE })
public @interface MetaAnnotation {}
package de.scrum_master.app;

import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

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

@Retention(RUNTIME)
@Target({ TYPE })
@MetaAnnotation
public @interface MyAnnotation {}

Примеры классов Java:

Один класс аннотируется метааннотацией, один - аннотированной аннотацией, а третий - без аннотации (отрицательный тестовый пример):

package de.scrum_master.app;

@MetaAnnotation
public class MyClassA {
  public void doSomething() {}
}
package de.scrum_master.app;

@MyAnnotation
public class MyClassB {
  public void doSomething() {}
}
package de.scrum_master.app;

public class MyClassC {
  public void doSomething() {}
}

Приложение драйвера:

Поскольку я использую чистый Java + AspectJ без Spring, я использую это небольшое приложение, чтобы продемонстрировать результат.

package de.scrum_master.app;

public class Application {
  public static void main(String[] args) {
    new MyClassA().doSomething();
    new MyClassB().doSomething();
    new MyClassC().doSomething();
  }
}

Аспект:

package de.scrum_master.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class MetaAnnotationInterceptor {
  @Before(
    "execution(* *(..)) && (" +
      "within(@de.scrum_master.app.MetaAnnotation *) || " +
      "within(@(@de.scrum_master.app.MetaAnnotation *) *)" +
    ")"
  )
  public void myAdvice(JoinPoint thisJoinPoint){
    System.out.println(thisJoinPoint);
  }
}

Журнал консоли:

execution(void de.scrum_master.app.MyClassA.doSomething())
execution(void de.scrum_master.app.MyClassB.doSomething())

Теперь, если вы хотите добавить еще один уровень вложенности, добавьте новую аннотацию и аннотируйте ею ранее не аннотированный класс:

package de.scrum_master.app;

import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

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

@Retention(RUNTIME)
@Target({ TYPE })
@MyAnnotation
public @interface MyOtherAnnotation {}
package de.scrum_master.app;

@MyOtherAnnotation
public class MyClassC {
  public void doSomething() {}
}

Затем расширьте pointcut еще на один уровень вложенности / рекурсии:

package de.scrum_master.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class MetaAnnotationInterceptor {
  @Before(
    "execution(* *(..)) && (" +
      "within(@de.scrum_master.app.MetaAnnotation *) || " +
      "within(@(@de.scrum_master.app.MetaAnnotation *) *) || " +
      "within(@(@(@de.scrum_master.app.MetaAnnotation *) *) *)" +
    ")"
  )
  public void myAdvice(JoinPoint thisJoinPoint){
    System.out.println(thisJoinPoint);
  }
}

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

execution(void de.scrum_master.app.MyClassA.doSomething())
execution(void de.scrum_master.app.MyClassB.doSomething())
execution(void de.scrum_master.app.MyClassC.doSomething())

P.S .: Часть execution(* *(..)) необходима только в AspectJ, чтобы ограничить сопоставление pointcut выполнением методов, потому что AspectJ может перехватывать больше событий, чем Spring AOP. Таким образом, в Spring AOP вы можете удалить эту часть и скобки, окружающие ... || ... || ... часть.

person kriegaex    schedule 31.01.2018
comment
спасибо kriegaex, но ваше второе решение, похоже, не компилируется даже после того, как я добавлю недостающую цитату. И что я действительно с нетерпением жду, так это универсальный способ, подходящий для любого класса или метода, аннотированного аннотацией, которая сама аннотируется другой конкретной аннотацией. - person maojf; 01.02.2018
comment
Извините, я написал это на своем айпаде, не тестировал. Я только что обновил свой ответ не только с отсутствующей двойной кавычкой, но и с отсутствующей парой круглых скобок, необходимой в этом случае из-за шаблона ..*. Что касается вашего второго вопроса, не могли бы вы отредактировать свой вопрос и заменить мой заполнитель примером или лучшим объяснением того, что вы имеете в виду? Я могу понять, чего вы хотите достичь, но я не уверен. - person kriegaex; 01.02.2018
comment
Я разработал свой вопрос, я был бы очень признателен, если бы вы могли предоставить решение, это меня беспокоит в течение нескольких дней. И еще раз спасибо, сейчас я использую ваше второе решение. @kriegaex - person maojf; 06.02.2018
comment
Снимаю шляпу перед kriegaex !! Вы не только решили мою проблему, но и дали мне ценный урок по Java Annotations и AOP. Ваше решение отлично работает в моем приложении. @kriegaex - person maojf; 07.02.2018
comment
Это отличный ответ. Спасибо, что научил меня чему-то новому. Я бы добавил только небольшую вещь: я думаю, что было бы неплохо сохранить часть execution(* *(..)) && , даже если вы используете только Spring AOP, потому что вы никогда не знаете заранее, что вам не нужно будет переключаться на AspectJ в будущем, и это способ сохранить семантическую совместимость pointcut друг с другом. - person Nándor Előd Fekete; 07.02.2018
comment
Не работает для меня, если используется для Spring '@RequestMapping' как мета и, например. «@GetMapping» в качестве моей аннотации. - person miracle_the_V; 09.11.2018
comment
@miracle_the_V, возможно, вы хотите создать новый вопрос с MCVE, показывающим, чего вы хотите достичь, как вы пытались и что не удалось. Я очень занят, но, может быть, я смогу ответить, если вы мне на это укажете. - person kriegaex; 23.11.2018