Spring 引导中 Class 级别和方法级别的注释方面(NullPointerException)
Annotation aspect for both Class Level and Method Level in Spring Boot (NullPointerException)
当我想为 @Transactional
.
这样的注释创建方面时,我遇到了一个有趣的问题
这里是控制器和注解:
@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
@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();
}
}
当我 运行 应用程序并点击 http://localhost:8080/hello 时,一切正常:
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
但是,如果我删除了方法上的注释:
@GetMapping("/hello")
public String helloController(){
final String methodName = "helloController";
callAnotherMethod();
LOGGER.info("HelloController for method : {}", methodName);
return "Hello";
}
然后simpleAnnotation
参数变为null,切面方法抛出NullPointerException。
在那之后,我像下面这样改变了方面的顺序,它开始工作了:
@Around(value = " @annotation(simpleAnnotation) || @within(simpleAnnotation)", argNames = "simpleAnnotation")
但是,在这种情况下,如果我删除 class 级别的注释并只放置方法级别,那么我将面临相同的 NPE。
我认为,在某种程度上,方面的条件会覆盖值。
我试图将 class 级别的注释建议和方法级别的建议分开,但在那种情况下,如果我在 class 和方法级别上都有注释,两者建议有效(我不想要)
我试过这样更新:
@Around(value = "@within(simpleAnnotation) || @annotation(simpleAnnotation) || @within(simpleAnnotation)", argNames = "simpleAnnotation")
这似乎可行,但这是一个好的解决方案吗?
编辑:此解决方案也不起作用。如果我在 class 和方法级别上都有注释,假设 class 级别注释值为 false,而方法级别为 true, 那么注释值将为 false.
由于 ||
,您的参数绑定不明确,因为它是(非排他性)OR 而不是 XOR,这意味着两个条件可能同时为真。想象一下 class 和方法都有注释。应该绑定哪一个?
另见 this answer。
即就像 R.G 所说的那样,您想对方法级和 class 级注释使用两个单独的切入点和建议。您仍然可以将重复代码提取到方面内的辅助方法中,并从两种建议方法中调用它。
要解决 NPE 问题,您可以将切入点指示符(@within
和 @annotation
)重构为同一方面内的两种不同建议方法。
基于 isAllowed
值的处理逻辑可以保存在一个公共方法中,并从两个建议方法中调用。
举例说明:
@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
}
}
更新 :修改了@kriegaex
评论的代码
当我想为 @Transactional
.
这里是控制器和注解:
@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
@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();
}
}
当我 运行 应用程序并点击 http://localhost:8080/hello 时,一切正常:
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
但是,如果我删除了方法上的注释:
@GetMapping("/hello")
public String helloController(){
final String methodName = "helloController";
callAnotherMethod();
LOGGER.info("HelloController for method : {}", methodName);
return "Hello";
}
然后simpleAnnotation
参数变为null,切面方法抛出NullPointerException。
在那之后,我像下面这样改变了方面的顺序,它开始工作了:
@Around(value = " @annotation(simpleAnnotation) || @within(simpleAnnotation)", argNames = "simpleAnnotation")
但是,在这种情况下,如果我删除 class 级别的注释并只放置方法级别,那么我将面临相同的 NPE。
我认为,在某种程度上,方面的条件会覆盖值。
我试图将 class 级别的注释建议和方法级别的建议分开,但在那种情况下,如果我在 class 和方法级别上都有注释,两者建议有效(我不想要)
我试过这样更新:
@Around(value = "@within(simpleAnnotation) || @annotation(simpleAnnotation) || @within(simpleAnnotation)", argNames = "simpleAnnotation")
这似乎可行,但这是一个好的解决方案吗?
编辑:此解决方案也不起作用。如果我在 class 和方法级别上都有注释,假设 class 级别注释值为 false,而方法级别为 true, 那么注释值将为 false.
由于 ||
,您的参数绑定不明确,因为它是(非排他性)OR 而不是 XOR,这意味着两个条件可能同时为真。想象一下 class 和方法都有注释。应该绑定哪一个?
另见 this answer。
即就像 R.G 所说的那样,您想对方法级和 class 级注释使用两个单独的切入点和建议。您仍然可以将重复代码提取到方面内的辅助方法中,并从两种建议方法中调用它。
要解决 NPE 问题,您可以将切入点指示符(@within
和 @annotation
)重构为同一方面内的两种不同建议方法。
基于 isAllowed
值的处理逻辑可以保存在一个公共方法中,并从两个建议方法中调用。
举例说明:
@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
}
}
更新 :修改了@kriegaex
评论的代码