Reputation: 948
I am just facing interesting issue when I want to create an aspect for annotation like @Transactional
.
Here is the controller and annotation:
@RestController
@SimpleAnnotation
public class HelloController{
private static final Logger LOGGER = LoggerFactory.getLogger(HelloController.class);
@GetMapping("/hello")
@SimpleAnnotation(isAllowed=true)
public String helloController(){
final String methodName = "helloController";
callAnotherMerhod();
LOGGER.info("HelloController for method : {}", methodName);
return "Hello";
}
private void callAnotherMethod(){
}
}
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface SimpleAnnotation {
boolean isAllowed() default false;
}
Aspect for that annotation is here:
@Aspect
@Component
public class SimpleAspect {
private static final Logger LOGGER = LoggerFactory.getLogger(SimpleAspect.class);
@Around(value = "@within(simpleAnnotation) || @annotation(simpleAnnotation)", argNames = "simpleAnnotation")
public Object simpleAnnotation(ProceedingJoinPoint proceedingJoinPoint, SimpleAnnotation simpleAnnotation) throws Throwable{
LOGGER.info("Simple annotation value: {}, ASPECT-LOG {}", simpleAnnotation.isAllowed(),proceedingJoinPoint.getSignature().getName());
return proceedingJoinPoint.proceed();
}
}
When I run the application and hit the http://localhost:8080/hello , everything is fine:
2020-11-09 11:36:48.230 INFO 8479 --- [nio-8080-exec-1] c.s.springaop.aspects.SimpleAspect : Simple annotation value: true, ASPECT-LOG helloController
2020-11-09 11:36:48.246 INFO 8479 --- [nio-8080-exec-1] c.s.s.controller.HelloController : HelloController for method : helloController
However if I removed annotation on the method:
@GetMapping("/hello")
public String helloController(){
final String methodName = "helloController";
callAnotherMethod();
LOGGER.info("HelloController for method : {}", methodName);
return "Hello";
}
Then simpleAnnotation
parameter becomes null, and aspect method throws NullPointerException.
After that, I changed the order of the aspect like the below, it start to work:
@Around(value = " @annotation(simpleAnnotation) || @within(simpleAnnotation)", argNames = "simpleAnnotation")
However, in this situation if I remove annotation on class level and just put only the method level, then i am facing the same NPE.
I think, in some way aspect's conditions overwrites the values.
I tried to separate the class level annotation advice and method level advice, but in that case if i have the annotation on both the class and method level, both advices work (which I do not want)
I tried to update like this:
@Around(value = "@within(simpleAnnotation) || @annotation(simpleAnnotation) || @within(simpleAnnotation)", argNames = "simpleAnnotation")
This seems to be working, but is it a good solution?
EDIT: This solution is not working also. If I have annotation both on the class and method level and let's say class level annotation value is false, and method's level is true, then annotation value will be false.
Upvotes: 2
Views: 1512
Reputation: 7131
To workaround the NPE , you may refactor the pointcut designators (@within
and @annotation
) to two different advice methods within the same aspect.
The logic to process based on the isAllowed
value can be held in a common method and called from both the advice methods.
To illustrate :
@Aspect
@Component
public class SimpleAspect {
@Around(value = "@annotation(simpleAnnotation) && !@within(my.package.SimpleAnnotation)", argNames = "simpleAnnotation")
public Object simpleAnnotationOnMethod(ProceedingJoinPoint proceedingJoinPoint, SimpleAnnotation simpleAnnotation)
throws Throwable {
System.out.println("Simple annotation value:" + simpleAnnotation.isAllowed() + " , ASPECT-LOG :"
+ proceedingJoinPoint.getSignature().getName());
process(simpleAnnotation);
return proceedingJoinPoint.proceed();
}
@Around(value = "@within(simpleAnnotation)", argNames = "simpleAnnotation")
public Object simpleAnnotationOnType(ProceedingJoinPoint proceedingJoinPoint, SimpleAnnotation simpleAnnotation)
throws Throwable {
System.out.println("Simple annotation value:" + simpleAnnotation.isAllowed() + " , ASPECT-LOG :"
+ proceedingJoinPoint.getSignature().getName());
process(simpleAnnotation);
return proceedingJoinPoint.proceed();
}
private void process(SimpleAnnotation simpleAnnotation) {
// advice logic
}
}
Update : Modified the code as commented by @kriegaex
Upvotes: 2
Reputation: 67457
Your parameter binding is ambiguous due to the ||
because it is a (non-exclusive) OR rather than an XOR, which means that both conditions could be true at the same time. Imagine both the class and the method have an annotation. Which one should be bound?
See also this answer.
I.e. that, just like R.G said, you want to use two separate pointcuts and advices for method- and class-level annotations. You can still factor out duplicate code into a helper method inside the aspect and call it from both advice methods.
Upvotes: 2