在使用 @Async 注解实现异步线程的时候,为了能够在同类中调用,使用AopContext获取类的实例,结果报错:
调用如下:
@GetMapping("test03")public void testAsync03() throws InterruptedException {log.info("=====03主线程执行start: " + Thread.currentThread().getName());AsyncTestController currentProxy = (AsyncTestController) AopContext.currentProxy();currentProxy.doAsync01();currentProxy.doAsync02();log.info("=====03主线程执行end: " + Thread.currentThread().getName());}@Async("defaultTaskExecutor")public void doAsync01() throws InterruptedException {Thread.sleep(3000);log.info("=====子线程001执行: " + Thread.currentThread().getName());}@Async("defaultTaskExecutor")public void doAsync02() {log.info("=====子线程002执行: " + Thread.currentThread().getName());}结果如下:

但是回头看看我的配置完全是OK的:
@EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true)
问题分析
找错的常用方法:逆推法。 首先我们找到报错的最直接原因:AopContext.currentProxy()这句代码报错的,因此有必要看看AopContext这个工具类:
public final class AopContext {private static final ThreadLocal<Object> currentProxy = new NamedThreadLocal("Current AOP proxy");private AopContext() {}// 该方法是public static方法,说明可以被任意类进行调用public static Object currentProxy() throws IllegalStateException {Object proxy = currentProxy.get();// 它抛出异常的原因是当前线程并没有绑定对象// 而给线程版定对象的方法在下面:特别有意思的是它的访问权限是default级别,也就是说只能Spring内部去调用~if (proxy == null) {throw new IllegalStateException("Cannot find current proxy: Set 'exposeProxy' property on Advised to 'true' to make it available.");} else {return proxy;}}// 它最有意思的地方是它的访问权限是default的,表示只能给Spring内部去调用~// 调用它的类有CglibAopProxy和JdkDynamicAopProxy@Nullablestatic Object setCurrentProxy(@Nullable Object proxy) {Object old = currentProxy.get();if (proxy != null) {currentProxy.set(proxy);} else {currentProxy.remove();}return old;}
} 从此工具源码可知,决定是否抛出所示异常的直接原因就是请求的时候setCurrentProxy()方法是否被调用过。通过寻找发现只有两个类会调用此方法,并且都是Spring内建的类且都是代理类的处理类:CglibAopProxy和JdkDynamicAopProxy.
说明:本文所有示例,都基于接口的代理,所以此处只以
JdkDynamicAopProxy作为代表进行说明即可.
我们知道在执行代理对象的目标方法的时候,都会交给InvocationHandler处理,因此做事情的在invoke()方法里:
@Nullablepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {Object oldProxy = null;boolean setProxyContext = false;TargetSource targetSource = this.advised.targetSource;Object target = null;
...if (this.advised.exposeProxy) {oldProxy = AopContext.setCurrentProxy(proxy);setProxyContext = true;}
...
} so,最终决定是否会调用set方法是由this.advised.exposeProxy这个值决定的,因此下面我们只需要关心ProxyConfig.exposeProxy这个属性值什么时候被赋值为true的就可以了。
ProxyConfig.exposeProxy这个属性的默认值是false。其实最终调用设置值的是同名方法Advised.setExposeProxy()方法,而且是通过反射调用的.
@EnableAspectJAutoProxy(exposeProxy = true)的作用
此注解它导入了AspectJAutoProxyRegistrar,最终设置此注解的两个属性的方法为:
class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {AspectJAutoProxyRegistrar() {}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")) {AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);}}}
}public abstract class AopConfigUtils {
...public static void forceAutoProxyCreatorToUseClassProxying(BeanDefinitionRegistry registry) {if (registry.containsBeanDefinition("org.springframework.aop.config.internalAutoProxyCreator")) {BeanDefinition definition = registry.getBeanDefinition("org.springframework.aop.config.internalAutoProxyCreator");definition.getPropertyValues().add("proxyTargetClass", Boolean.TRUE);}}public static void forceAutoProxyCreatorToExposeProxy(BeanDefinitionRegistry registry) {if (registry.containsBeanDefinition("org.springframework.aop.config.internalAutoProxyCreator")) {BeanDefinition definition = registry.getBeanDefinition("org.springframework.aop.config.internalAutoProxyCreator");definition.getPropertyValues().add("exposeProxy", Boolean.TRUE);}}...
}看到此注解标注的属性值最终都被设置到了internalAutoProxyCreator身上,也就是进而重要的一道菜:自动代理创建器。
在此各位小伙伴需要先明晰的是:@Async的代理对象并不是由自动代理创建器来创建的,而是由AsyncAnnotationBeanPostProcessor一个单纯的BeanPostProcessor实现的。
这让我想起了类似的注解: @EnableTransactionManagement ,该注解向容器注入的是自动代理创建器InfrastructureAdvisorAutoProxyCreator,所以exposeProxy = true对它的代理对象都是生效的,因此可以正运行,另外,@EnableCaching也是可以的。
@EnableAsync给容器注入的是AsyncAnnotationBeanPostProcessor,它用于给@Async生成代理,但是它仅仅是个BeanPostProcessor并不属于自动代理创建器,因此exposeProxy = true对它无效。 所以AopContext.setCurrentProxy(proxy);这个set方法肯定就不会执行,so但凡只要业务方法中调用AopContext.currentProxy()方法就铁定抛异常。
将@Transaction 和 @Async 放在一起使用:
@GetMapping("test03")public void testAsync03() throws InterruptedException {log.info("=====03主线程执行start: " + Thread.currentThread().getName());AsyncTestController currentProxy = (AsyncTestController) AopContext.currentProxy();currentProxy.doAsync01();currentProxy.doAsync02();log.info("=====03主线程执行end: " + Thread.currentThread().getName());}@Async("defaultTaskExecutor")@Transactionalpublic void doAsync01() throws InterruptedException {Thread.sleep(3000);log.info("=====子线程001执行: " + Thread.currentThread().getName());}@Async("defaultTaskExecutor")@Transactionalpublic void doAsync02() {log.info("=====子线程002执行: " + Thread.currentThread().getName());}
}运行结果如下:

这个示例的结论,相信是很多小伙伴都没有想到的。仅仅只是加入了事务,@Asycn竟然就能够完美的使用AopContext.currentProxy()获取当前代理对象了。
为了便于理解,我分步骤讲述如下,不出意外你肯定就懂了:
- AsyncAnnotationBeanPostProcessor在创建代理时有这样一个逻辑:若已经是- Advised对象了,那就只需要把- @Async的增强器添加进去即可。若不是代理对象才会自己去创建
public abstract class AbstractAdvisingBeanPostProcessor extends ProxyProcessorSupport implements BeanPostProcessor {@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) {if (bean instanceof Advised) {advised.addAdvisor(this.advisor);return bean;}// 上面没有return,这里会继续判断自己去创建代理~}
}- 自动代理创建器AbstractAutoProxyCreator它实际也是个BeanPostProcessor,所以它和上面处理器的执行顺序很重要~~~
- 两者都继承自ProxyProcessorSupport所以都能创建代理,且实现了Ordered接口 1.AsyncAnnotationBeanPostProcessor默认的order值为Ordered.LOWEST_PRECEDENCE。但可以通过@EnableAsync指定order属性来改变此值。 执行代码语句:bpp.setOrder(this.enableAsync.<Integer>getNumber("order"));2.AbstractAutoProxyCreator默认值也同上。但是在把自动代理创建器添加进容器的时候有这么一句代码:beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE);自动代理创建器这个处理器是最高优先级
- 由上可知因为标注有@Transactional,所以自动代理会生效,因此它会先交给AbstractAutoProxyCreator把代理对象生成好了,再交给后面的处理器执行
- 由于AbstractAutoProxyCreator先执行,所以AsyncAnnotationBeanPostProcessor执行的时候此时Bean已经是代理对象了,由步骤1可知,此时它会沿用这个代理,只需要把切面添加进去即可~
从上面步骤可知,加上了事务注解,最终代理对象是由自动代理创建器创建的,因此exposeProxy = true对它有效,这是解释它能正常work的最为根本的原因。@Transactional只为了创建代理对象而已,所在放在哪儿对@Async的作用都不会有本质的区别。
有个示例非常非常有意思,因此我特意拿出来讲解一下。将两个注解都加到了入口方法:
@GetMapping("test03")@Async("defaultTaskExecutor")@Transactionalpublic void testAsync03() throws InterruptedException {log.info("=====03主线程执行start: " + Thread.currentThread().getName());AsyncTestController currentProxy = (AsyncTestController) AopContext.currentProxy();currentProxy.doAsync01();currentProxy.doAsync02();log.info("=====03主线程执行end: " + Thread.currentThread().getName());}public void doAsync01() throws InterruptedException {Thread.sleep(3000);log.info("=====子线程001执行: " + Thread.currentThread().getName());}public void doAsync02() {log.info("=====子线程002执行: " + Thread.currentThread().getName());}咋一看其实以为是没有问题的,毕竟正常我们会这么思考:执行funTemp()方法会启动异步线程执行,同时它会把Proxy绑定在当前线程中,所以即使是新起的异步线程也有能够使用AopContext.currentProxy()才对。
但有意思的地方就在此处:它报错了,正所谓你以为的不一定就是你以为的。 解释:根本原因就是关键节点的执行时机问题。在执行代理对象funTemp方法的时候,绑定动作oldProxy = AopContext.setCurrentProxy(proxy);在前,目标方法执行(包括增强器的执行)invocation.proceed()在后。so其实在执行绑定的还是在主线程里而并非是新的异步线程,所以在你在方法体内(已经属于异步线程了)执行AopContext.currentProxy()那可不就报错了嘛~
解决方案
对上面现象原因可以做一句话的总结:@Async要想顺利使用AopContext.currentProxy()获取当前代理对象来调用本类方法,需要确保你本Bean已经被自动代理创建器提前代理。
在实际业务开发中:只要的类标注有
@Transactional或者@Caching等注解,就可以放心大胆的使用吧
知晓了原因,解决方案从来都是信手拈来的事。 不过如果按照如上所说需要隐式依赖这种方案我非常的不看好,总感觉不踏实,也总感觉报错迟早要来。(比如某个同学该方法不要事务了/不要缓存了,把对应注解摘掉就瞬间报错了,到时候你可能哭都没地哭诉去~)
备注:
墨菲定律在开发过程中从来都没有不好使过~~~程序员兄弟姐妹们应该深有感触吧
下面根据我个人经验,介绍一种解决方案中的最佳实践:
遵循的最基本的原则是:显示的指定比隐式的依赖来得更加的靠谱、稳定
终极解决方案:
@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {BeanDefinition beanDefinition = beanFactory.getBeanDefinition(TaskManagementConfigUtils.ASYNC_ANNOTATION_PROCESSOR_BEAN_NAME);beanDefinition.getPropertyValues().add("exposeProxy", true);}
}这样我们可以在@Async和AopContext.currentProxy()就自如使用了,不再对别的啥的有依赖性~
其实我认为最佳的解决方案是如下两个(都需要Spring框架做出修改): 1、
@Async的代理也交给自动代理创建器来完成 2、@EnableAsync增加exposeProxy属性,默认值给false即可(此种方案的原理同我示例的最佳实践~)
总结
最后再总结两点,小伙伴们使用的时候稍微注意下就行:
- 请不要在异步线程里使用AopContext.currentProxy()
- AopContext.currentProxy()不能使用在非代理对象所在方法体内
















