简体   繁体   中英

Spring AOP custom annotation, getting Null with annotation arguments

I am using this custom annotation for logging execution time, annotation could be present on method or class in which all public methods have it. Everything works fine, except in case of method level "LogExecutionTime logExecutionTime" comes null. This throws an 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());
      }
    }
  }

if I reverse the order-

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

the behavior reverses and I get a valid object at method level and null at class level annotated methods.

I have worked around this by having 2 explicit methods and separating the two-

@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());
      }
    }

Was hoping to understand this behavior, when we use OR '||'with two pointcuts.

class level

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

method level

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

I came to run you example, and reproduce the same example as yours, when it come to runtime expression is same weird because when you specify the annotation on class level and you write this expression

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

The point cut will evaluate to true for you class (event you annotation its available in joinPoint.getTarget().getClass().getAnnotations() , )

Now when it come to binding the variable the compiler check all your expressions that mean binding @within(logExecutionTime) to variable logExecutionTime and @annotation(logExecutionTime) to the same variable , if the method is not annotated it will ge null, => override the initial with, that cause all senarios you mention.

Try to put this expression @within(logExecutionTime) || @annotation(logExecutionTime) || @within(logExecutionTime) @within(logExecutionTime) || @annotation(logExecutionTime) || @within(logExecutionTime) @within(logExecutionTime) || @annotation(logExecutionTime) || @within(logExecutionTime) and you'll get you variable not null which prove what i said, last @within(logExecutionTime) override what precedent

The key here is that the logic applied to select the point cut matching not the same when it come context-binding

Now when it come to AOP point-cut you must be careful and follow best practice as the spring team they mention here to avoid weird runtime results

Cheers

This cannot work, it does not even compile with the AspectJ compiler. Maybe in your IDE and with Spring AOP you do not see any warnings or errors, but I see:

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

This means that it is not clear which annotation should be selected if eg both the class and the method contain an instance of that annotation. It is, as the error message said, ambiguous. But ambiguous parameter bindings across || are not permitted. They can also happen if you try to bind values from different "or" branches to a single parameter in an args() list.

I had the same problem. What you want is exactly same as Spring @Transcriptional behaves (I mean, class level or method level annotation with parameters). I used your solution but to get the class level parameter value (as the annotation object received null), I used reflection. I know it is a dirty solution! But I tried other solutions and couldn't find!

Her is the full code. This will call the advice code either the annotation is used on a class or a method. If the annotation is placed on both (class and method), the method takes the precedence.

@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();
}

Let's know if you got a cleaner solution!

The problem with your workaround appears when you annotate both a class and a method with the annotation, resulting in triggering both of them.

To prevent it declare the class level advice as:

@Around("!@annotation(LogExecutionTime) && @within(logExecutionTime)")

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM