前言
本文标题包含有'靓丽'的字眼:Spring框架bug。相信有的小伙伴心里小九九就会说了:又是一篇标题党文章。
鉴于此,此处可以很负责任的对大伙说:本人所有文章绝不哗众取宠,除了干货只剩干货。
相信关注过我的小伙伴都是知道的,我只递送干货,绝不标题党来浪费大家的时间和精力~那无异于谋财害命(说得严重了,不喜勿喷)
关于标题党的好与坏、优与劣,此处我不置可否
本篇文章能让你知道exposeProxy=true真实作用和实际作用范围,从而能够在开发中更精准的使用到它。
背景
这篇文章可定位为是基于上篇文章的续文:
【小家Spring】使用@Async异步注解导致该Bean在循环依赖时启动报BeanCurrentlyInCreationException异常的根本原因分析,以及提供解决方案
本来一切本都那么平静,直到我用了@Async注解,好多问题都接踵而至(上篇文章已经解决大部分了)。在上篇文章中,为了解决@Async同类方法调用问题我提出了两个方向的解决方案:
- 自己注入自己,然后再调用接口方法(当然此处的一个变种是使用编程方式形如:AInterface a = applicationContext.getBean(AInterface.class);这样子手动获取也是可行的~~~本文不讨论这种比较直接简单的方式)
- 使用AopContext.currentProxy();方式
方案一上篇文章已花笔墨重点分析,毕竟方案一我认为更为重要些。本文分析使用方案二的方式,它涉及到AOP、代理对象的暴露,因此我认为本文的内容对你平时开发的影响是不容小觑,可以重点浏览咯~
我相信绝大多数小伙伴都遇到过这个异常:
java.lang.IllegalStateException: Cannot find current proxy: Set 'exposeProxy' property on Advised to 'true' to make it available. at org.springframework.aop.framework.AopContext.currentProxy(AopContext.java:69) at com.fsx.dependency.B.funTemp(B.java:14) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:343) at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:206) at com.sun.proxy.$Proxy44.funTemp(Unknown Source) ...
然后当你去靠度娘搜索解决方案时,发现无一例外
都教你只需要这么做就成:
@EnableAspectJAutoProxy(exposeProxy = true)
本文我想说的可能又是一个技术敏感性问题,其实绝大多数情况下你按照这么做是可行的,直到你遇到了@Async也需要调用本类方法的时候,你就有点绝望了,然后本文或许会成为了你的救星~
本以为加了exposeProxy = true就能顺风顺水了,但它却出问题了:依旧报如上的异常信息。如果你看到这里也觉得不可思议,那么本文就更能体现它的价值所在~
此问题我个人把它归类为Spring的bug我觉得是无可厚非的,因为它的语义与实际表现出来的结果想悖了,so我把定义为Spring框架的bug。
对使用者来说,标注了exposeProxy = true,理论上就应该能够通过AopContext.currentProxy()拿到代理对象,可惜Spring这里却掉链子了,有点名不副实之感~
示例
本文将以多个示例来模拟不同的使用case,首先从直观的结果上先了解@EnableAspectJAutoProxy(exposeProxy = true)的作用以及它存在的问题。
备注:下面所有示例都建立在@EnableAspectJAutoProxy(exposeProxy = true)已经开启的前提下,形如:
@Configuration @EnableAspectJAutoProxy(exposeProxy = true) // 暴露当前代理对象到当前线程绑定 public class RootConfig { }
示例一
此示例大都用于解决事务不生效
问题上(同类方法调用引起的事务不生效,关于Spring事务不生效的case,可以参考:【小家java】Spring事务不生效的原因大解读 )。
@Service public class B implements BInterface { @Transactional @Override public void funTemp() { ... // 希望调用本类方法 但是它抛出异常,希望也能够回滚事务 BInterface b = BInterface.class.cast(AopContext.currentProxy()); System.out.println(b); b.funB(); } @Override public void funB() { // ... 处理业务属于 System.out.println(1 / 0); } }
结论:能正常work,事务也会生效~
示例二
同类内方法调用,希望异步执行被调用的方法(希望@Async
生效)
@Service public class B implements BInterface { @Override public void funTemp() { System.out.println("线程名称:" + Thread.currentThread().getName()); // 希望调用本类方法 但是希望它去异步执行~ BInterface b = BInterface.class.cast(AopContext.currentProxy()); System.out.println(b); b.funB(); } @Async @Override public void funB() { System.out.println("线程名称:" + Thread.currentThread().getName()); } }
结论:执行即报错
java.lang.IllegalStateException: Cannot find current proxy: Set 'exposeProxy' property on Advised to 'true' to make it available.
示例三
同类内方法调用,希望异步执行被调用的方法,并且在入口方法处使用事务
@Service public class B implements BInterface { @Transactional @Override public void funTemp() { System.out.println("线程名称:" + Thread.currentThread().getName()); // 希望调用本类方法 但是希望它去异步执行~ BInterface b = BInterface.class.cast(AopContext.currentProxy()); System.out.println(b); b.funB(); } @Async @Override public void funB() { System.out.println("线程名称:" + Thread.currentThread().getName()); } }
结论:正常work没有报错,@Async异步生效、事务也生效
示例四
和示例三
的唯一区别是把事务注解@Transactional
标注在被调用的方法处(和@Async
同方法):
@Service public class B implements BInterface { @Override public void funTemp() { System.out.println("线程名称:" + Thread.currentThread().getName()); // 希望调用本类方法 但是希望它去异步执行~ BInterface b = BInterface.class.cast(AopContext.currentProxy()); System.out.println(b); b.funB(); } @Transactional @Async @Override public void funB() { System.out.println("线程名称:" + Thread.currentThread().getName()); } }
结论:同示例三
示例五
把@Async
标注在入口方法上:
@Service public class B implements BInterface { @Transactional @Async @Override public void funTemp() { System.out.println("线程名称:" + Thread.currentThread().getName()); BInterface b = BInterface.class.cast(AopContext.currentProxy()); System.out.println(b); b.funB(); } @Override public void funB() { System.out.println("线程名称:" + Thread.currentThread().getName()); } }
结论:请求即报错
java.lang.IllegalStateException: Cannot find current proxy: Set 'exposeProxy' property on Advised to 'true' to make it available. at org.springframework.aop.framework.AopContext.currentProxy(AopContext.java:69)