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.
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