使用aop类级别注释时,spring为错误的类创建代理

使用aop类级别注释时,spring为错误的类创建代理

问题描述:

在将Spring AOP与类级别注释一起使用时,spring context.getBean似乎总是为每个类创建并返回代理或拦截器,无论它们是否具有注释.

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.

此行为仅用于类级别的注释.对于方法级注释或执行切入点,如果不需要进行拦截,则getBean将返回POJO.

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
      }
    }

安迪·布朗(Andy Brown)是正确的,这是设计使然.原因是根据 AspectJ手册切入点指示符,例如@args@this@target@within@withincode@annotation(或可用的子集) (在Spring AOP中)用于根据运行时中是否存在注释进行匹配.这就是为什么在Spring调试日志中看到为所有可能需要方面功能的组件创建了代理的原因.

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

如果bean的类或其任何方法都不带有注释,则不会创建任何代理.该建议会同时检测两种类型的注释,但如果同时存在这两种方法,则建议使用方法注释.

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.

更新:当然,您可以在Spring内使用完整的AspectJ来代替此替代方法,而完全避免使用代理.

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