Springboot原始碼分析之代理物件內嵌呼叫
摘要:
關於這個話題可能最多的是@Async
和@Transactional
一起混用,我先解釋一下什麼是代理物件內嵌呼叫,指的是一個代理方法呼叫了同類的另一個代理方法。首先在這兒我要宣告事務直接的巢狀呼叫除外,至於為什麼,是它已經將資訊儲存線上程級別了,是不是又點兒抽象,感覺吃力,可以看看我前面關於事務的介紹。
@Async和@Transactional共存
@Component public class AsyncWithTransactional { @Async @Transactional public void test() { } }
這樣一段程式碼會發生什麼?熟悉的人都會感覺疑惑,都有效果麼?誰先被代理增強?
自動代理建立器AbstractAutoProxyCreator
它實際也是個BeanPostProcessor
,所以它們的執行順序很重要~~~
兩者都繼承自
ProxyProcessorSupport
所以都能建立代理,且實現了Ordered
介面- - -- - ---AsyncAnnotationBeanPostProcessor
預設的order
值為Ordered.LOWEST_PRECEDENCE
。但可以通過@EnableAsync
指定order
屬性來改變此值。AsyncAnnotationBeanPostProcessor
Advised
物件了,那就只需要把@Async
的增強器新增進去即可。若不是代理物件才會自己去建立
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,這裡會繼續判斷自己去建立代理~ } }
AbstractAutoProxyCreator
預設值也同上。但是在把自動代理建立器新增進容器的時候有這麼一句程式碼:beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE);
自動代理建立器這個處理器是最高優先順序- 由上可知因為標註有
@Transactional
,所以自動代理會生效,因此它會先交給AbstractAutoProxyCreator
把代理物件生成好了,再交給後面的處理器執行
由於AbstractAutoProxyCreator
先執行,所以AsyncAnnotationBeanPostProcessor
執行的時候此時Bean
已經是代理物件了,此時它會沿用這個代理,只需要把切面新增進去即可~
方法呼叫順序影響
想必大家都知道一點就是同類的方法呼叫只有入口方法被代理才會被增強,這是由於原始碼級別只處理入口方法呼叫,是你的話你也這樣設計,不然方法棧那麼深,你管得了那麼多嗎?既然知道了這個原因,那麼我們接下來在看一下後面的列子。
沿用代理物件
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)
咦,是不是我們可以從容器中獲取代理物件呢?沒有錯,從容器獲取代理物件也是一種沿用代理物件來呼叫方法鏈的手段,但是你會用麼?依賴於代理的具體實現而書寫程式碼,這樣移植性會非常差的。
揭祕@EnableAspectJAutoProxy(exposeProxy = true)
Spring
內建的類且都是代理類的處理類:CglibAopProxy
和JdkDynamicAopProxy
兩者很類似,在處理這個邏輯上。所以此處只以JdkDynamicAopProxy
作為代表進行說明即可。
我們知道在執行代理物件的目標方法的時候,都會交給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);
}
}
}
}
最終決定是否會呼叫set
方法是由this.advised.exposeProxy
這個值決定的,因此下面我們只需要關心ProxyConfig.exposeProxy
這個屬性值什麼時候被賦值為true
的就可以了。
ProxyConfig.exposeProxy
這個屬性的預設值是false
。其實最終呼叫設定值的是同名方法Advised.setExposeProxy()
方法,而且是通過反射呼叫的,再次強調 看清楚後置處理器,@EnableAspectJAutoProxy(exposeProxy = true)
作用的範圍在AbstractAutoProxyCreator
建立器,非同步註解和快取註解等就不行了,怎麼解決後面在分析。
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 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() {
//不使用代理物件呼叫的話,後續方法不會被增強
AsyncWithTransactional asyncWithTransactional = AsyncWithTransactional.class.cast(AopContext.currentProxy());
asyncWithTransactional.async();
}
@Async
public void async() {
}
}
這樣都完全ok的,但是如果換一下呢就會跑出異常。
子執行緒引起的問題
@Transactional//@Transactional有此註解和沒有毫無關係
@Async
public void transactional() {
AsyncWithTransactional asyncWithTransactional = AsyncWithTransactional.class.cast(AopContext.currentProxy());
asyncWithTransactional.async();
}
public void async() {
}
根本原因就是關鍵節點的執行時機問題。在執行代理物件transactional
方法的時候,先執行繫結動作AopContext.setCurrentProxy(proxy);
然後目標方法執行(包括增強器的執行)invocation.proceed()
。其實在執行繫結的還是在主執行緒裡而並非是新的非同步執行緒,所以在你在方法體內(已經屬於非同步執行緒了)執行AopContext.currentProxy()
那可不就報錯了嘛~
所以入口方法用了類似@Async
的效果註解都會導致代理物件繫結不對,繼而導致呼叫錯誤。
如何解決類似子執行緒引起的問題呢?
@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
也可以基於這樣的思想來解決,以上就是我的簡單例子,但是配合我的文字說明,相信大家可以舉一反三,隨意玩弄它們之間的呼叫關係。
其實如果Spring做出原始碼改變會更好的解決這個問題
@Async
的代理也交給自動代理建立器來完成(Spring做出原始碼改變)@EnableAsync
增加exposeProxy
屬性,預設值給false
即可(Spring做出原始碼改變)
總結:
不要在非同步執行緒裡使用
AopContext.currentProxy()
AopContext.currentProxy()
不能使用在非代理物件所在方法體內