【问题标题】:Spring AOP custom annotation, getting Null with annotation argumentsSpring AOP 自定义注解,使用注解参数获取 Null
【发布时间】:2018-02-08 05:59:39
【问题描述】:

我正在使用这个自定义注解来记录执行时间,注解可以出现在所有公共方法都有它的方法或类上。一切正常,除非方法级别“LogExecutionTime logExecutionTime”为空。这会引发 NPE。

@Around("@annotation(logExecutionTime) || @within(logExecutionTime)")
public Object logExecutionTime(ProceedingJoinPoint joinPoint, LogExecutionTime logExecutionTime) throws Throwable {
    final Logger logger = LoggerFactory.getLogger(joinPoint.getTarget().getClass());

    final String name = joinPoint.toShortString();
    final StopWatch stopWatch = new StopWatch(name);

    stopWatch.start(name);
    try {
      return joinPoint.proceed();

    } finally {
      stopWatch.stop();
      if (logExecutionTime.value()) {
        logger.info(joinPoint.getSignature().getName() + ".time=", stopWatch.getTotalTimeSeconds());
      }
    }
  }

如果我颠倒顺序-

@Around("@within(logExecutionTime) || @annotation(logExecutionTime)")

行为相反,我在方法级别获得了一个有效对象,在类级别注释方法获得了 null。

我已经通过使用 2 个显式方法并将两者分开来解决这个问题 -

@Around("@within(logExecutionTime)")
public Object logExecutionTimeClassLevel(ProceedingJoinPoint joinPoint, LogExecutionTime logExecutionTime) throws Throwable {
    return logExecutionTimeMethodLevel(joinPoint, logExecutionTime);
  }

@Around("@annotation(logExecutionTime)")
public Object logExecutionTimeMethodLevel(ProceedingJoinPoint joinPoint, LogExecutionTime logExecutionTime) throws Throwable {
    final Logger logger = LoggerFactory.getLogger(joinPoint.getTarget().getClass());

    final String name = joinPoint.toShortString();
    final StopWatch stopWatch = new StopWatch(name);

    stopWatch.start(name);
    try {
      return joinPoint.proceed();

    } finally {
      stopWatch.stop();
      if (logExecutionTime.value()) {
        logger.info(joinPoint.getSignature().getName() + ".time=", stopWatch.getTotalTimeMillis());
      }
    }

希望了解这种行为,当我们使用 OR '||'有两个切入点。

班级水平

@LogExecutionTime
@Component
public class CleanUpService implements ICleanUpService { ... }

方法层

@Scheduled(fixedDelay = 100)
@LogExecutionTime(false)
public void processMessageQueue() { ... }

【问题讨论】:

  • 您能否与我们分享您的目标方法和注释的实现,以便我可以重现您的用例
  • @elmehdi 请立即查看

标签: java spring spring-boot annotations spring-aop


【解决方案1】:

我来运行您的示例,并重现与您相同的示例,当涉及到运行时表达式时,同样奇怪,因为当您在类级别指定注释并编写此表达式时

@Around(" @within(logExecutionTime) || @annotation(logExecutionTime) ")

切入点将为您的班级评估为真(如果您在joinPoint.getTarget().getClass().getAnnotations()中注释它可用,)

现在在绑定变量时,编译器会检查所有表示将 @within(logExecutionTime) 绑定到变量 logExecutionTime 和 @annotation(logExecutionTime) 到同一个变量的表达式,如果方法没有注释,它将ge null, => 覆盖初始值,这会导致您提到的所有情况。

试着把这个表达式写成@within(logExecutionTime) || @annotation(logExecutionTime) || @within(logExecutionTime) 你会得到你的变量不是 null 这证明了我所说的,最后 @within(logExecutionTime) 覆盖什么先例

这里的关键是在上下文绑定时选择切入点匹配的逻辑不一样

现在,当涉及到 AOP 切入点时,您必须小心并遵循 Spring 团队的最佳实践,他们提到了 here,以避免出现奇怪的运行时结果

干杯

【讨论】:

  • 谢谢,所以你在这里推荐什么,我已经查看了文档,无法弄清楚或者有 2 个单独的建议是更好的主意?
  • 把两个方法和同一个切面都同时触发,这不是你想要的,将inside和annotation结合起来会产生歧义,必须避免,如果你想构建完整的日志系统,你可以在单独的方面(advise)定义 logExecutionTimeClassLevel,在另一个方面定义 logExecutionTimeMethodLevel,这样您就可以控制执行顺序(顺序注释)以首先触发类通知,然后是方法并在方法级别方面添加测试以确保它是目标类没有注释,看起来有点复杂,但它可以解决问题-
  • 如果您想了解我的意思的完整示例,请告诉我
【解决方案2】:

这行不通,它甚至不能用 AspectJ 编译器编译。也许在您的 IDE 和 Spring AOP 中您看不到任何警告或错误,但我看到了:

ambiguous binding of parameter(s) logExecutionTime across '||' in pointcut

这意味着不清楚应该选择哪个注释,例如类和方法都包含该注释的实例。正如错误消息所说,它是模棱两可的。但是不允许在|| 之间进行模棱两可的参数绑定。如果您尝试将来自不同“或”分支的值绑定到 args() 列表中的单个参数,也会发生这种情况。

【讨论】:

    【解决方案3】:

    我遇到了同样的问题。您想要的与 Spring @Transcriptional 的行为完全相同(我的意思是,带有参数的类级别或方法级别注释)。我使用了您的解决方案,但为了获取类级别参数值(因为注释对象收到 null),我使用了反射。我知道这是一个肮脏的解决方案!但我尝试了其他解决方案,但找不到!

    她是完整的代码。这将在类或方法上使用注释时调用通知代码。如果注释同时放在(类和方法)上,则方法优先。

    @Aspect
    @Configurable
    @Component
    public class DynamicValueAspect {
    
    
        @Around(" @within(dynamicValueAnnotation) || @annotation(dynamicValueAnnotation))")
        public Object process(ProceedingJoinPoint joinPoint, DynamicValue dynamicValueAnnotation) throws Throwable {
    
            String annotationParameter;
            if (dynamicValueAnnotation == null) { //class level annotation
                annotationParameter = extractClassLevelAnnotationParameterValue(joinPoint);
            } else {
                annotationParameter = dynamicValueAnnotation.myAnnotationParameter();
            }
    
            System.out.println("    " + annotationParameter);//get the annotation parameter value
            return joinPoint.proceed();
        }
    
        private String extractClassLevelAnnotationParameterValue(ProceedingJoinPoint joinPoint) {
    
            Annotation[] classAnnotations = joinPoint.getTarget().getClass().getAnnotations();
            for (Annotation annotation : classAnnotations) {
                if (annotation.annotationType() == DynamicValue.class) {
                    return ((DynamicValue) annotation).myAnnotationParameter();
                }
            }
    
            throw new RuntimeException("No DynamicValue value annotation was found");
        }
    }
    
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.METHOD, ElementType.TYPE})
    public @interface DynamicValue {
        String myAnnotationParameter();
    }
    

    让我们知道您是否有更清洁的解决方案!

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-05-30
      • 1970-01-01
      • 2011-07-14
      • 1970-01-01
      • 1970-01-01
      • 2015-08-23
      • 2011-06-13
      相关资源
      最近更新 更多