2

When using spring AOP with class level annotations, spring context.getBean seems to always create and return a proxy or interceptor for every class, wether they have the annotation or not.

This behavior is only for class level annotation. For method level annotations, or execution pointcuts, if there is no need for interception, getBean returns a POJO.

Is this a bug? As designed? Or am I doing something wrong?

@Component
@Aspect
public class AspectA {
  @Around("@target(myAnnotation)")
  public Object process(ProceedingJoinPoint jointPoint, MyAnnotation myAnnotation) throws Throwable {
    System.out.println(
      "AspectA: myAnnotation target:" + jointPoint.getTarget().getClass().getSimpleName());
    System.out.println(" condition:" + myAnnotation.condition());
    System.out.println(" key:" + myAnnotation.key());
    System.out.println(" value:" + myAnnotation.value());
    return jointPoint.proceed();
  }
}
@Component("myBean2")
//@MyAnnotation(value="valtest-classLevel2", key="keytest-classLevel2", condition="contest-classLevel2")
public class MyBean2 {
  public Integer testAspectCallInt(int i) {
    System.out.println("MyBean2.testAspectCallInt(i=" + i + ")");
    return i + 1000;
  }
}
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
public @interface MyAnnotation {
  String value() default "";
  String key() default "";
  String condition() default "";
}
@ComponentScan()
@EnableAspectJAutoProxy
public class Test {
  public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(Test.class);
    MyBean2 bean = (MyBean2) ctx.getBean("myBean2");
    System.out.println(bean.getClass());  // prints CGLIB proxy, even when annotation is commented out on class
    bean.testAspectCallInt(12); // calling method
  }
}
Gonen I
  • 5,576
  • 1
  • 29
  • 60
  • I'm sure it's by design. Are you looking to get access to the 'real' bean behind the proxy? If so then call `AopUtils.getTargetClass(proxyBean);` – Andy Brown Oct 26 '18 at 08:08
  • @AndyBrown No, it causes some errors, for example CGLIB fails when trying to create a proxy of an unrelated singleton class, which does not have the annotation, but has a private constructor. – Gonen I Oct 26 '18 at 09:10

1 Answers1

7

Andy Brown is right, it is by design. The reason is that according to the AspectJ manual pointcut designators such as @args, @this, @target, @within, @withincode, and @annotation (or the subset of those available in Spring AOP) are used to match based on the presence of an annotation at runtime. This is why in the Spring debug log you see that proxies are created for all components which might need aspect functionality.

If you want to avoid that, you can refactor your aspect into something like this at the cost of an uglier pointcut and even uglier reflection in the advice code:

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.annotation.Annotation;

@Component
@Aspect
public class AspectA {
  @Around("execution(* (@MyAnnotation *).*(..)) || execution(@MyAnnotation * *(..))")
  public Object process(ProceedingJoinPoint joinPoint) throws Throwable {
    MyAnnotation myAnnotation = null;
    for (Annotation annotation : ((MethodSignature) joinPoint.getSignature()).getMethod().getDeclaredAnnotations()) {
      if (annotation instanceof MyAnnotation) {
        myAnnotation = (MyAnnotation) annotation;
        break;
      }
    }
    if (myAnnotation == null) {
      myAnnotation = joinPoint.getTarget().getClass().getAnnotationsByType(MyAnnotation.class)[0];
    }
    System.out.println("AspectA: myAnnotation target:" + joinPoint.getTarget().getClass().getSimpleName());
    System.out.println(" condition:" + myAnnotation.condition());
    System.out.println(" key:" + myAnnotation.key());
    System.out.println(" value:" + myAnnotation.value());
    return joinPoint.proceed();
  }
}

If neither the bean's class nor any of its methods bears the annotation, no proxy will be created. The advice detects both types of annotation, but prefers a method annotation if both are present.

Update: Instead of this workaround you could of course use full AspectJ from within Spring and avoid proxies altogether.

kriegaex
  • 63,017
  • 15
  • 111
  • 202
  • Thanks. But when using @Around("@annotation(MyAnnotation)") , for a method level annotation, getBean returns a proxy only if the annotation is actually present on one of the class methods, and otherwise returns the original POJO. It is odd that as soon as you define any class level Annotation advice, getBean for all classes, whether having the annotation or not, will start returning proxies. – Gonen I Nov 23 '18 at 23:13
  • 2
    Well, Spring AOP uses heuristics, the so-called AspectJ "fast match" feature, here in order to avoid runtime overhead for pointcut matching. Sometimes a fast match returns "maybe", which is interpreted as "yes", so you can expect to get some false positived there. Check out e.g. [this similar issue](https://jira.spring.io/browse/SPR-13329) and the related link to an AspectJ mailing list thread. BTW, I actually debugged through it and saw that fast match is responsible for your problem. – kriegaex Nov 24 '18 at 07:00
  • You saved me with your answer! – Vitalii Apr 25 '20 at 08:44