AopContext使用问题分析

"AopContext使用问题分析"

Posted by tablesheep on November 11, 2021

version:Spring Framework 5.3.x

在使用Spring开发时,偶尔会遇到在本类方法中调用自身被AOP增强的方法,这时候直接调用并不会达到预期的效果,大致的原因是在Spring生成的代理对象中调用自身方法并不会调用被增强的方法,而是会直接调用被代理类原本的方法(具体可通过Spring JDK动态代理和CGLIB代理生成的字节码去看)。

那具体有什么方法能实现我们想要的效果呢?(🙄)

  • 通过依赖查找获取Bean(简单粗暴)
  • 不要写这种代码(废话,不过官方也是这么讲的,不过个人也是建议这样)
  • 通过配置exposeProxy=true,使用AopContext获取当前代理对象调用(Spring官方提供的一种解决方案,但是在一些情况下有坑)

AopContext使用

now,先来说说AopContext的用法,以及它碰到@Async时会遇到的问题。

使用

只需要通过EnableAspectJAutoProxy#exposeProxy开启,使用AopContext#currentProxy就可以获取到当前的代理对象

1
2
3
4
5
//配置
@EnableAspectJAutoProxy(exposeProxy = true)

//使用
((YourClass)AopContext.currentProxy()).yourMethod();

原理

AopContext就是使用ThreadLocal在同一个线程中使用,而关键一点就是在何时调用setCurrentProxy将代理对象设置进去。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public final class AopContext {
   private static final ThreadLocal<Object> currentProxy = new NamedThreadLocal<>("Current AOP proxy");
......

   @Nullable
   static Object setCurrentProxy(@Nullable Object proxy) {
      Object old = currentProxy.get();
      if (proxy != null) {
         currentProxy.set(proxy);
      }
      else {
         currentProxy.remove();
      }
      return old;
   }

}

而具体设置的时机便是在AOP方法调用时进行的,以JdkDynamicAopProxy为例,在invoke中会根据advised.exposeProxyAdvisedSupport)的配置进行设置,而这个配置便是在JdkDynamicAopProxy构造时进行设置的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
@Override
@Nullable
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
   Object oldProxy = null;
   boolean setProxyContext = false;
......

   try {
     ......

      Object retVal;

      if (this.advised.exposeProxy) {
         // Make invocation available if necessary.
         oldProxy = AopContext.setCurrentProxy(proxy);
         setProxyContext = true;
      }
......

      // Get the interception chain for this method.
      List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

      // Check whether we have any advice. If we don't, we can fallback on direct
      // reflective invocation of the target, and avoid creating a MethodInvocation.
      if (chain.isEmpty()) {
         // We can skip creating a MethodInvocation: just invoke the target directly
         // Note that the final invoker must be an InvokerInterceptor so we know it does
         // nothing but a reflective operation on the target, and no hot swapping or fancy proxying.
         Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
         retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
      }
      else {
         // We need to create a method invocation...
         MethodInvocation invocation =
               new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
         // Proceed to the joinpoint through the interceptor chain.
         retVal = invocation.proceed();
      }

      // Massage return value if necessary.
      Class<?> returnType = method.getReturnType();
      if (retVal != null && retVal == target &&
            returnType != Object.class && returnType.isInstance(proxy) &&
            !RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
         // Special case: it returned "this" and the return type of the method
         // is type-compatible. Note that we can't help if the target sets
         // a reference to itself in another returned object.
         retVal = proxy;
      }
      else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {
         throw new AopInvocationException(
               "Null return value from advice does not match primitive return type for: " + method);
      }
      return retVal;
   }
   finally {
......
      if (setProxyContext) {
         // Restore old proxy.
         AopContext.setCurrentProxy(oldProxy);
      }
   }
}

问题

问题一

1
2
//配置
@EnableAspectJAutoProxy(exposeProxy = true)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Service
public class ServiceImpl implements IService {

    @Override
    public void handle() {
        //do something ......
        ((IService)AopContext.currentProxy()).asyncHandle();
    }

    @Async
    @Override
    public void asyncHandle() {
        //do something ......
    }
}

Bean中只存在@Async时,在别的方法使用AopContext试图调用@Async增强的方法时会出现如下错误:

1
Cannot find current proxy: Set 'exposeProxy' property on Advised to 'true' to make it available, and ensure that AopContext.currentProxy() is invoked in the same thread as the AOP invocation context.

原因分析

根据上面对AopContext的分析,结合@Async的创建(使用AsyncAnnotationBeanPostProcessor)过程,在AbstractAdvisingBeanPostProcessor#postProcessAfterInitialization打断点,会使用prepareProxyFactory创建ProxyFactory,而ProxyFactory的配置来自自身,而exposeProxy是为false

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
public abstract class AbstractAdvisingBeanPostProcessor extends ProxyProcessorSupport implements BeanPostProcessor {
......

   @Override
   public Object postProcessAfterInitialization(Object bean, String beanName) {
      if (this.advisor == null || bean instanceof AopInfrastructureBean) {
         // Ignore AOP infrastructure such as scoped proxies.
         return bean;
      }

      if (bean instanceof Advised) {
          //若对象已经被代理过了,则直接将advisor添加进去
         Advised advised = (Advised) bean;
         if (!advised.isFrozen() && isEligible(AopUtils.getTargetClass(bean))) {
            // Add our local Advisor to the existing proxy's Advisor chain...
            if (this.beforeExistingAdvisors) {
               advised.addAdvisor(0, this.advisor);
            }
            else {
               advised.addAdvisor(this.advisor);
            }
            return bean;
         }
      }

      if (isEligible(bean, beanName)) {
          //创建ProxyFactory
         ProxyFactory proxyFactory = prepareProxyFactory(bean, beanName);
         if (!proxyFactory.isProxyTargetClass()) {
            evaluateProxyInterfaces(bean.getClass(), proxyFactory);
         }
         proxyFactory.addAdvisor(this.advisor);
         customizeProxyFactory(proxyFactory);

         // Use original ClassLoader if bean class not locally loaded in overriding class loader
         ClassLoader classLoader = getProxyClassLoader();
         if (classLoader instanceof SmartClassLoader && classLoader != bean.getClass().getClassLoader()) {
            classLoader = ((SmartClassLoader) classLoader).getOriginalClassLoader();
         }
          //创建代理对象
         return proxyFactory.getProxy(classLoader);
      }

      // No proxy needed.
      return bean;
   }

......
   protected ProxyFactory prepareProxyFactory(Object bean, String beanName) {
   //创建ProxyFactory,将本身配置赋予创建的ProxyFactory
      ProxyFactory proxyFactory = new ProxyFactory();
      proxyFactory.copyFrom(this);
      proxyFactory.setTarget(bean);
      return proxyFactory;
   }
......
}

而我们已经配置了@EnableAspectJAutoProxy(exposeProxy = true),这个时候就要看看这个配置是如何工作的了。在@EnableAspectJAutoProxy中引入了AspectJAutoProxyRegistrar,这个类实现了ImportBeanDefinitionRegistrar,会在BeanDefinition注册时进行工作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {

   /**
    * Register, escalate, and configure the AspectJ auto proxy creator based on the value
    * of the @{@link EnableAspectJAutoProxy#proxyTargetClass()} attribute on the importing
    * {@code @Configuration} class.
    */
   @Override
   public void registerBeanDefinitions(
         AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

      AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);

      AnnotationAttributes enableAspectJAutoProxy =
            AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
      if (enableAspectJAutoProxy != null) {
         if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
            AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
         }
         if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
             //若exposeProxy为true,调用AopConfigUtils#forceAutoProxyCreatorToExposeProxy进行配置
            AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
         }
      }
   }

}
1
2
3
4
5
6
7
8
9
10
public static final String AUTO_PROXY_CREATOR_BEAN_NAME =
			"org.springframework.aop.config.internalAutoProxyCreator";

public static void forceAutoProxyCreatorToExposeProxy(BeanDefinitionRegistry registry) {
   if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
       //可以看出,它配置的只有名为AUTO_PROXY_CREATOR_BEAN_NAME的BeanDefinition
      BeanDefinition definition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
      definition.getPropertyValues().add("exposeProxy", Boolean.TRUE);
   }
}

由于配置的只有名为AUTO_PROXY_CREATOR_BEAN_NAME变量的BeanDefinition,而AsyncAnnotationBeanPostProcessor配置时并不是这个名字,所以@EnableAspectJAutoProxy(exposeProxy = true)并不会对它生效。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class ProxyAsyncConfiguration extends AbstractAsyncConfiguration {

   @Bean(name = TaskManagementConfigUtils.ASYNC_ANNOTATION_PROCESSOR_BEAN_NAME)
   @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
   public AsyncAnnotationBeanPostProcessor asyncAdvisor() {
      Assert.notNull(this.enableAsync, "@EnableAsync annotation metadata was not injected");
      AsyncAnnotationBeanPostProcessor bpp = new AsyncAnnotationBeanPostProcessor();
      bpp.configure(this.executor, this.exceptionHandler);
      Class<? extends Annotation> customAsyncAnnotation = this.enableAsync.getClass("annotation");
      if (customAsyncAnnotation != AnnotationUtils.getDefaultValue(EnableAsync.class, "annotation")) {
         bpp.setAsyncAnnotationType(customAsyncAnnotation);
      }
      bpp.setProxyTargetClass(this.enableAsync.getBoolean("proxyTargetClass"));
      bpp.setOrder(this.enableAsync.<Integer>getNumber("order"));
      return bpp;
   }

}

解决

参照AopConfigUtils#orceAutoProxyCreatorToExposeProxy只需要实现BeanFactoryPostProcessor,将AsyncAnnotationBeanPostProcessorBeanDefinition中添加exposeProxy=true即可。

1
2
3
4
5
6
7
8
9
10
@Configuration
public class AsyncBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        if (beanFactory.containsBeanDefinition(TaskManagementConfigUtils.ASYNC_ANNOTATION_PROCESSOR_BEAN_NAME)) {
            BeanDefinition beanDefinition = beanFactory.getBeanDefinition(TaskManagementConfigUtils.ASYNC_ANNOTATION_PROCESSOR_BEAN_NAME);
            beanDefinition.getPropertyValues().add("exposeProxy", true);
        }
    }
}

总结

@EnableAspectJAutoProxy(exposeProxy = true) 只适用于名为AUTO_PROXY_CREATOR_BEAN_NAMEBeanDefinition

问题二

1
2
//配置
@EnableAspectJAutoProxy(exposeProxy = true)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Service
public class ServiceImpl implements IService {

    @Override
    public void handle() {
        //do something ......
        ((IService)AopContext.currentProxy()).asyncHandle();
    }

    @Async
    @Override
    public void asyncHandle() {
        //do something ......
    }
    
    @Transactional(rollbackFor = Exception.class)
   // @Cacheable(cacheNames="cachename", key="#a0.id")
    @Override
    public void save(A a) {
        //do something ......
    }
}

Bean中存在@Async还有别的Spring AOP注解(@Transactional@Cacheable......)时,在别的方法使用AopContext试图调用@Async增强的方法时却不会出现问题,诡异,吗?

现象分析

这次依旧在AbstractAdvisingBeanPostProcessor#postProcessAfterInitialization打断点,会发现exposeProxy是依旧为false,但是与之前不同的是并不会进入创建代理对象的阶段,而是会直接将advisor添加进Advised中,这表示这个bean已经被代理过了,再看Advised中的配置,会发现exposeProxy却是true,再通过看Advised中的advisors,可以看出这个Bean是被事务增强(或者缓存增强)过的Bean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public abstract class AbstractAdvisingBeanPostProcessor extends ProxyProcessorSupport implements BeanPostProcessor {
......

   @Override
   public Object postProcessAfterInitialization(Object bean, String beanName) {
......
      if (bean instanceof Advised) {
          //若对象已经被代理过了,则直接将advisor添加进去
         Advised advised = (Advised) bean;
         if (!advised.isFrozen() && isEligible(AopUtils.getTargetClass(bean))) {
            // Add our local Advisor to the existing proxy's Advisor chain...
            if (this.beforeExistingAdvisors) {
               advised.addAdvisor(0, this.advisor);
            }
            else {
               advised.addAdvisor(this.advisor);
            }
            return bean;
         }
      }
......
   }
}

出现这个现象的原因是由于在AbstractAdvisingBeanPostProcessor中会直接向已代理的Bean中直接添加Advisor,同时AsyncAnnotationBeanPostProcessor会位于BeanPostProcessors最后(@EnableAsync中指定了order,默认在最后),而AopConfigUtils中注入的名为AUTO_PROXY_CREATOR_BEAN_NAMEBeanPostProcessor会在最前面。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public abstract class AopConfigUtils {

......

   @Nullable
   private static BeanDefinition registerOrEscalateApcAsRequired(
         Class<?> cls, BeanDefinitionRegistry registry, @Nullable Object source) {

      Assert.notNull(registry, "BeanDefinitionRegistry must not be null");

      if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
         BeanDefinition apcDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
         if (!cls.getName().equals(apcDefinition.getBeanClassName())) {
            int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName());
            int requiredPriority = findPriorityForClass(cls);
            if (currentPriority < requiredPriority) {
               apcDefinition.setBeanClassName(cls.getName());
            }
         }
         return null;
      }

      RootBeanDefinition beanDefinition = new RootBeanDefinition(cls);
      beanDefinition.setSource(source);
    //指定order顺序,默认最高优先级
      beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE);
      beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
      registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition);
      return beanDefinition;
   }
......

}

总结

在创建Bean时,如果Bean中有@Cacheable、@Transactional、@Async注解,会先经由名为AUTO_PROXY_CREATOR_BEAN_NAME(自动代理创建器)的BeanPostProcessor代理,接着才会经过AsyncBeanFactoryPostProcessor的代理往代理对象中添加异步的Advisor。而此时的代理对象的配置是来自AUTO_PROXY_CREATOR_BEAN_NAME代理而来,故如果配置了exposeProxy=true会发现没有问题。