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。

我认为,在某种程度上,方面的条件会覆盖值。

    @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

评论的代码