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

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

问题描述:

当使用带有类级别注释的 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.