新聞中心
本篇文章給大家分享的是有關(guān)Springboot中怎么調(diào)用代理對象內(nèi)嵌,小編覺得挺實用的,因此分享給大家學(xué)習(xí),希望大家閱讀完這篇文章后可以有所收獲,話不多說,跟著小編一起來看看吧。
十載的涼山州網(wǎng)站建設(shè)經(jīng)驗,針對設(shè)計、前端、開發(fā)、售后、文案、推廣等六對一服務(wù),響應(yīng)快,48小時及時工作處理。成都全網(wǎng)營銷推廣的優(yōu)勢是能夠根據(jù)用戶設(shè)備顯示端的尺寸不同,自動調(diào)整涼山州建站的顯示方式,使網(wǎng)站能夠適用不同顯示終端,在瀏覽器中調(diào)整網(wǎng)站的寬度,無論在任何一種瀏覽器上瀏覽網(wǎng)站,都能展現(xiàn)優(yōu)雅布局與設(shè)計,從而大程度地提升瀏覽體驗。創(chuàng)新互聯(lián)從事“涼山州網(wǎng)站設(shè)計”,“涼山州網(wǎng)站推廣”以來,每個客戶項目都認(rèn)真落實執(zhí)行。
@Async和@Transactional共存
@Component public class AsyncWithTransactional { @Async @Transactional public void test() { } }
這樣一段代碼會發(fā)生什么?熟悉的人都會感覺疑惑,都有效果么?誰先被代理增強(qiáng)?
自動代理創(chuàng)建器AbstractAutoProxyCreator
它實際也是個BeanPostProcessor
,所以它們的執(zhí)行順序很重要~~~
兩者都繼承自
ProxyProcessorSupport
所以都能創(chuàng)建代理,且實現(xiàn)了Ordered
接口- - -- - ---AsyncAnnotationBeanPostProcessor
默認(rèn)的order
值為Ordered.LOWEST_PRECEDENCE
。但可以通過@EnableAsync
指定order
屬性來改變此值。AsyncAnnotationBeanPostProcessor
在創(chuàng)建代理時有這樣一個邏輯:若已經(jīng)是Advised
對象了,那就只需要把@Async
的增強(qiáng)器添加進(jìn)去即可。若不是代理對象才會自己去創(chuàng)建
public abstract class AbstractAdvisingBeanPostProcessor extends ProxyProcessorSupport implements BeanPostProcessor { @Override public Object postProcessAfterInitialization(Object bean, String beanName) { if (bean instanceof Advised) { advised.addAdvisor(this.advisor); return bean; } // 上面沒有return,這里會繼續(xù)判斷自己去創(chuàng)建代理~ } }
AbstractAutoProxyCreator
默認(rèn)值也同上。但是在把自動代理創(chuàng)建器添加進(jìn)容器的時候有這么一句代碼:beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE);
自動代理創(chuàng)建器這個處理器是最高優(yōu)先級由上可知因為標(biāo)注有
@Transactional
,所以自動代理會生效,因此它會先交給AbstractAutoProxyCreator
把代理對象生成好了,再交給后面的處理器執(zhí)行 由于AbstractAutoProxyCreator
先執(zhí)行,所以AsyncAnnotationBeanPostProcessor
執(zhí)行的時候此時Bean
已經(jīng)是代理對象了,此時它會沿用這個代理,只需要把切面添加進(jìn)去即可~
方法調(diào)用順序影響
想必大家都知道一點(diǎn)就是同類的方法調(diào)用只有入口方法被代理才會被增強(qiáng),這是由于源碼級別只處理入口方法調(diào)用,是你的話你也這樣設(shè)計,不然方法棧那么深,你管得了那么多嗎?既然知道了這個原因,那么我們接下來在看一下后面的列子。
沿用代理對象
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) ...
這個異常在上述情況最容易出現(xiàn),然而解決的方法都是@EnableAspectJAutoProxy(exposeProxy = true)
咦,是不是我們可以從容器中獲取代理對象呢?沒有錯,從容器獲取代理對象也是一種沿用代理對象來調(diào)用方法鏈的手段,但是你會用么?依賴于代理的具體實現(xiàn)而書寫代碼,這樣移植性會非常差的。
揭秘@EnableAspectJAutoProxy(exposeProxy = true)
Spring
內(nèi)建的類且都是代理類的處理類:CglibAopProxy
和JdkDynamicAopProxy
兩者很類似,在處理這個邏輯上。所以此處只以JdkDynamicAopProxy
作為代表進(jìn)行說明即可。
我們知道在執(zhí)行代理對象的目標(biāo)方法的時候,都會交給InvocationHandler
處理,因此做事情的在invoke()
方法里:
final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable { ... @Override @Nullable public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { ... if (this.advised.exposeProxy) { // Make invocation available if necessary. oldProxy = AopContext.setCurrentProxy(proxy); setProxyContext = true; } ... finally { if (setProxyContext) { // Restore old proxy. AopContext.setCurrentProxy(oldProxy); } } } }
最終決定是否會調(diào)用set
方法是由this.advised.exposeProxy
這個值決定的,因此下面我們只需要關(guān)心ProxyConfig.exposeProxy
這個屬性值什么時候被賦值為true
的就可以了。
ProxyConfig.exposeProxy
這個屬性的默認(rèn)值是false
。其實最終調(diào)用設(shè)置值的是同名方法Advised.setExposeProxy()
方法,而且是通過反射調(diào)用的,再次強(qiáng)調(diào) 看清楚后置處理器,@EnableAspectJAutoProxy(exposeProxy = true)
作用的范圍在AbstractAutoProxyCreator
創(chuàng)建器,異步注解和緩存注解等就不行了,怎么解決后面在分析。
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); } //處理是否設(shè)置了該屬性 if (enableAspectJAutoProxy.getBoolean("exposeProxy")) { AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry); } } } }
看一下是如何設(shè)置屬性值的,我們后面可以采用這樣的方式來設(shè)置
public static void forceAutoProxyCreatorToExposeProxy(BeanDefinitionRegistry registry) { if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) { BeanDefinition definition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME); definition.getPropertyValues().add("exposeProxy", Boolean.TRUE); } }
什么時候使用的呢?
AopContext.setCurrentProxy(@Nullable Object proxy)
在CglibAopProxy
和JdkDynamicAopProxy
代理都有使用。
案例分析
@Component public class AsyncWithTransactional { //入口方法 @Transactional public void transactional() { //不使用代理對象調(diào)用的話,后續(xù)方法不會被增強(qiáng) AsyncWithTransactional asyncWithTransactional = AsyncWithTransactional.class.cast(AopContext.currentProxy()); asyncWithTransactional.async(); } @Async public void async() { } }
這樣都完全ok的,但是如果換一下呢就會跑出異常。
子線程引起的問題
@Transactional//@Transactional有此注解和沒有毫無關(guān)系 @Async public void transactional() { AsyncWithTransactional asyncWithTransactional = AsyncWithTransactional.class.cast(AopContext.currentProxy()); asyncWithTransactional.async(); } public void async() { }
根本原因就是關(guān)鍵節(jié)點(diǎn)的執(zhí)行時機(jī)問題。在執(zhí)行代理對象transactional
方法的時候,先執(zhí)行綁定動作AopContext.setCurrentProxy(proxy);
然后目標(biāo)方法執(zhí)行(包括增強(qiáng)器的執(zhí)行)invocation.proceed()
。其實在執(zhí)行綁定的還是在主線程里而并非是新的異步線程,所以在你在方法體內(nèi)(已經(jīng)屬于異步線程了)執(zhí)行AopContext.currentProxy()
那可不就報錯了嘛~
所以入口方法用了類似@Async
的效果注解都會導(dǎo)致代理對象綁定不對,繼而導(dǎo)致調(diào)用錯誤。
如何解決類似子線程引起的問題呢?
@Component public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor { @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { BeanDefinition beanDefinition = beanFactory.getBeanDefinition(TaskManagementConfigUtils.ASYNC_ANNOTATION_PROCESSOR_BEAN_NAME); beanDefinition.getPropertyValues().add("exposeProxy", true); } }
這樣解決了@Async
的綁定問題,@EnableCaching
也可以基于這樣的思想來解決,以上就是我的簡單例子,但是配合我的文字說明,相信大家可以舉一反三,隨意玩弄它們之間的調(diào)用關(guān)系。 其實如果Spring做出源碼改變會更好的解決這個問題
@Async
的代理也交給自動代理創(chuàng)建器來完成(Spring做出源碼改變)@EnableAsync
增加exposeProxy
屬性,默認(rèn)值給false
即可(Spring做出源碼改變)
總結(jié):
不要在異步線程里使用
AopContext.currentProxy()
AopContext.currentProxy()
不能使用在非代理對象所在方法體內(nèi)
以上就是Springboot中怎么調(diào)用代理對象內(nèi)嵌,小編相信有部分知識點(diǎn)可能是我們?nèi)粘9ぷ鲿姷交蛴玫降?。希望你能通過這篇文章學(xué)到更多知識。更多詳情敬請關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。
分享名稱:Springboot中怎么調(diào)用代理對象內(nèi)嵌
標(biāo)題鏈接:http://fisionsoft.com.cn/article/ihhgop.html