IllegalStateException: Cannot find current proxy: Set 'exposeProxy' property on Advised to 'true'

article/2025/10/29 18:22:50

在使用 @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内建的类且都是代理类的处理类CglibAopProxyJdkDynamicAopProxy.

说明:本文所有示例,都基于接口的代理,所以此处只以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()获取当前代理对象了。

为了便于理解,我分步骤讲述如下,不出意外你肯定就懂了:

  1. 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,这里会继续判断自己去创建代理~}
}
  1. 自动代理创建器AbstractAutoProxyCreator它实际也是个BeanPostProcessor,所以它和上面处理器的执行顺序很重要~~~
  2. 两者都继承自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); 自动代理创建器这个处理器是最高优先级
  3. 由上可知因为标注有@Transactional,所以自动代理会生效,因此它会先交给AbstractAutoProxyCreator把代理对象生成好了,再交给后面的处理器执行
  4. 由于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);}
}

这样我们可以在@AsyncAopContext.currentProxy()就自如使用了,不再对别的啥的有依赖性~

其实我认为最佳的解决方案是如下两个(都需要Spring框架做出修改): 1、@Async的代理也交给自动代理创建器来完成 2、@EnableAsync增加exposeProxy属性,默认值给false即可(此种方案的原理同我示例的最佳实践~

总结

最后再总结两点,小伙伴们使用的时候稍微注意下就行:

  1. 请不要在异步线程里使用AopContext.currentProxy()
  2. AopContext.currentProxy()不能使用在非代理对象所在方法体内

 


http://chatgpt.dhexx.cn/article/DfOaZG0f.shtml

相关文章

ADVISOR - 1

文章目录 1. ADVISOR 是什么1. 2 ADVISOR 可以用来做什么1.3 ADVISOR 不能做什么 1. ADVISOR 是什么 它是基于 MATLAB/Simulink 开发的一系列模型、数据以及脚本&#xff0c;能帮助我们快速分析传统车、纯电车以及混动车的性能和油耗。 能找到的最新的 ADVISOR 版本是 2003&a…

Advisor-Advisee Relationship

一、实习目的及要求 此次实习的任务中有Advisor-advisee Relationships (AARs)和通过分别计算AAR的概率而取得的共同作者关系。在数据中&#xff0c;用八个4位代码表示作者。 数据从合作双方的学术经历&#xff0c;合作经历的角度提取了特征。例如&#xff0c;如果已知A与B在…

ADV

悦谷首城 我司是专业的房产服务公司&#xff0c;主要从事北京拆迁安置房买卖中介、房屋租赁中介、代办房屋权证、房屋产权过户手续、垫资开发商房款&#xff0c;收拆迁房子指标&#xff0c;房产信息咨询等业务。公司经营上追求规模化、规范化、服务一体化。公司本着“诚信为本…

【Spring源码三千问】Advice、Advisor、Advised都是什么接口?

Advice、Advisor、Advised都是什么接口&#xff1f; 前言版本约定正文Advice、Advisor、Advised 类图AdviceMethodInterceptor AdvisorPointcutAdvisor AdvisedSpring AOP 代理类可以转换为 Advised 类型 小结 前言 在看 Spring AOP 的源码时&#xff0c;经常可以看到 Advice、…

【小家Spring】Spring AOP原理使用的基础类打点(AopInfrastructureBean、ProxyProcessorSupport、Advised、AjType)

每篇一句 没被洪水灌溉过&#xff0c;你怎么能证明这个城市的下水道架构师合理的呢&#xff1f; 相关阅读 【小家Spring】探索Spring AOP中aopalliance的Joinpoint、MethodInvocation、Interceptor、MethodInterceptor… 【小家Spring】Spring AOP原理使用的基础类打点&#x…

Spring AOP名词解释Advice,Pointcut,Advisor,Joinpoint,Advised是什么?

Advice advice解释为通知&#xff0c;需要执行的增强逻辑方法。 advice规范接口如图&#xff0c;分为了before&#xff0c;after等等&#xff0c;为什么没有around呢&#xff0c;因为是通过interceptor实现的。 这里可以看出&#xff0c;AfterAdvice最终会被转换包装成AfterR…

Advised,Advisor,Advice,Pointcut

2019独角兽企业重金招聘Python工程师标准>>> Advised->在Spring中创建了AOP代理之后&#xff0c;就能够使用org.springframework.aop.framework.Advised接口对它们进行管理。 任何AOP代理都能够被转型为这个接口&#xff0c;不论它实现了哪些其它接口 Advisor-&g…

Spring AOP编程官方文档解读之操作Advised对象

Spring AOP编程官方文档解读目录 文章目录 org.springframework.aop.framework.Advised是啥&#xff1f;看一下下面这个类结构图 可以看到无论是ProxyFactoryBean还是ProxyFactory最终都实现了这个接口、这个接口中的方法非常的多&#xff0c;我们随便看一下 public interfac…

arduino知识点梳理(二)——INPUT_PULLUP模式

参考文章1 参考文章2 arduino的引脚模式有三种&#xff1a; INPUT——输入模式OUTPUT——输出模式INPUT_PULLUP——输入上拉模式 Arduino 微控制器自带内部上拉电阻。如果需要使用内部上拉电阻&#xff0c;则应使用pinMode()将引脚设置为输入上拉&#xff08;INPUT_PULLUP&am…

Pull Up Field(字段上移)

两个子类拥有形同的字段 重构&#xff1a;将该字段移至超类

HAL库 output level 和 pull up/ pull down 的区别

从标准库过来的朋友可能对这两个选型有点分不清除 GPIO output level 即输出模式下初始化后端口的电位(高或低) GPIO Pull-up/Pull-down 即输入模式下的电位&#xff0c;例如按键检测

处理概括关系之一 :Pull Up Field(值域上移)

两个subclasses 拥有相同的值域。 将此一值域移至superclass。 动机&#xff08;Motivation&#xff09; 如果各个subclass 是分别开发的&#xff0c;或者是在重构过程中组合起来的&#xff0c;你常会发现它们拥有重复特性&#xff0c;特别是值域更容易重复。这样的值域有时拥有…

Push pull, open drain circuit, pull up, pull down resistor

Push pull 就以下面這個 電路來說&#xff0c; 因為沒有 pull up resistor&#xff0c; 所以 output voltage 由 low 往 high 的速度會較快。有兩個電晶體&#xff0c;一個on&#xff0c;一個 off&#xff0c; 可以 current sourcing&#xff0c;也可以 current sinking&#x…

上拉(Pull Up )或下拉(Pull Down)电阻详解

上拉&#xff08;Pull Up &#xff09;或下拉&#xff08;Pull Down&#xff09;电阻&#xff08;两者统称为“拉电阻”&#xff09;最基本的作用是&#xff1a;将状态不确定的信号线通过一个电阻将其箝位至高电平&#xff08;上拉&#xff09;或低电平&#xff08;下拉&#x…

BetterScroll 2.x 的 pulldown 使用、pullup使用

官方文档&#xff1a;https://better-scroll.github.io/docs/zh-CN/plugins/ BetterScrol滚动原理 有唯一的子节点子节点必须超出包裹的高度new BScroll&#xff08;‘容器名’&#xff09; html内容必须已经渲染完成 绿色部分为 wrapper&#xff0c;也就是父容器&#xff…

【超详细】output level 和 pull up/ pull down 的真正区别

目录 【超详细】output level 和 pull up/ pull down 的真正区别1. Output Level2. Pull up/ Pull down3. 两者的区别1&#xff09;对于Output口来说2&#xff09;而对于Input口来说 总结参考资料 【超详细】output level 和 pull up/ pull down 的真正区别 今天在使用stm32f1…

OUTPUT,INPUT,INPUT_PULLUP三种模式的区别

一&#xff0c;首先三种模式都需要由pinMode()函数来设置。 &#xff08;1&#xff09;设置引脚 为输出&#xff08;OUTPUT&#xff09;模式&#xff0c;此时引脚为低阻抗状态&#xff0c;可以向其他电路原件提供电流&#xff08;通常为40mA以内&#xff09; &#xff08;2&a…

Pull Up Method(函数上移)

动机 避免行为重复是很重要的。尽管重复的两个函数也可以各自工作得很好&#xff0c;但重复自身只会成为错误的滋生地&#xff0c;此外别无价值。无论何时&#xff0c;只要系统之内出现重复&#xff0c;你就会面临“修改其中一个却未能修改另一个”的风险。通常&#xff0c;找…

GPIO_PULLUP,PULLDOWN, NOPULL

NOPULL: 对于输出IO&#xff0c;配置为NOPULL&#xff0c;当IO输出高电平时&#xff0c;IO为1&#xff0c; 当IO输出低电平时&#xff0c;IO为0&#xff1b; PULLUP&#xff1a; 对于输入IO, 默认为高电平&#xff0c;当需要改变为低电平时配置为PULLUP。 比如&#xff1a;K…

pullup和pulldown在verilog中的使用方法

0 前言 这段时间涉及到了IO-PAD&#xff0c;在IO-PAD的RTL的时候注意到了pullup和pulldown&#xff0c;对这个知识比较好奇&#xff0c;就研究了一下&#xff0c;顺便记录下来&#xff0c;IO-PAD的内容等我再研究研究再考虑记录吧 >_< 1 pullup和pulldown的介绍 pullu…