I have an annotation which can be placed on a class or a method:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface TestAspectAnnotation {
String[] tags() default {};
}
I want to have a single advising method to handle both class-level and method-level usages:
@Around(value = "@annotation(annotation) || @within(annotation)", argNames = "pjp,annotation")
public Object testAdvice(ProceedingJoinPoint pjp,
TestAspectAnnotation annotation) throws Throwable {
String[] tags = annotation.tags();
Stopwatch stopwatch = Stopwatch.createStarted();
Object proceed = pjp.proceed();
stopwatch.stop();
long executionTime = stopwatch.elapsed(TimeUnit.MILLISECONDS);
sendMetrics(tags, executionTime);
return proceed;
}
This works fine when I annotate a class with TestAspectAnnotation(tags="foo")
.
However, if I annotate a method, annotation
argument will be null
.
Interestingly, if I reverse the order of the pointcut designators ("@within(annotation) || @annotation(annotation)"
), then I will have the reverse problem: method-level annotations will work fine, but class-level annotations will result in null
for for the annotation
argument.
Is there a way have a single pointcut and advice to support the annotation at both a class-level and method-level?
Is there a way have a single pointcut and advice
I had a similar problem recently and have tried various options, but to no avail. I ended up splitting the "or" pointcut into two separate pointcuts and calling the same method from both pieces of advice.
I have set up a small demo project to illustrate the working solution that I had set up. I hope this will help you:
@Component
@Aspect
public class SomeAspect {
@Around(value = "@within(annotation)", argNames = "pjp,annotation")
public Object methodAdvice(ProceedingJoinPoint pjp, SomeAnnotation annotation) throws Throwable {
return logTags(pjp, annotation);
}
@Around(value = "@annotation(annotation)", argNames = "pjp,annotation")
public Object classAdvice(ProceedingJoinPoint pjp, SomeAnnotation annotation) throws Throwable {
return logTags(pjp, annotation);
}
private Object logTags(ProceedingJoinPoint pjp, SomeAnnotation annotation) throws Throwable {
Stream.of(annotation.tags()).forEach(System.out::println);
return pjp.proceed();
}
}
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface SomeAnnotation {
String[] tags() default {};
}
@Service
@SomeAnnotation(tags = {"c", "d"})
public class SomeService {
@SomeAnnotation(tags = {"a", "b"})
public void test() {
}
}
@SpringBootApplication
public class Application implements ApplicationRunner {
@Autowired private SomeService someService;
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Override
public void run(ApplicationArguments args) throws Exception {
someService.test();
}
}