Spring AOP: is there a way to make @target work for indirect annotations?

这一生的挚爱 提交于 2019-12-04 20:46:37

I recreated the situation with pure AspectJ because I do not like Spring AOP so much. This is why I added an extra execution(* *(..)) && in front of the advice's pointcut in order to avoid matching other joinpoints unavailable in Spring AOP, such as call(). You can remove it in Spring AOP if you like.

Okay, let's create this situation as you described it:

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");
  }
}

This is our pure Java driver application (no Spring):

package de.scrum_master.app;

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

And this is the aspect:

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();
  }
}

The log output would be:

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

So far, so good. But what if we add another level of nesting?

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();
  }
}

Then the pointcut would not match the nested meta/stereotype annotation anymore:

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

We would have to explicitly add || @target(de.scrum_master.app.StereotypeAnnotation) to the pointcut, i.e. we would have to know all annotation class names in the hierarchy. There is a way to overcome this using a special syntax for the within() pointcut designator, see also my other answer here:

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();
  }
}

The console log changes to:

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

See? We only need to know one annotation class, namely MyAnnotation, in order to cover two nesting levels of meta annotations. Adding more levels would be straightforward. I admit that this kind of annotation nesting seems pretty much contrived, I just wanted to explain to you which options you have.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!