Springboot源碼分析之代理對象內嵌調用

摘要:

關於這個話題可能最多的是@Async@Transactional一塊兒混用,我先解釋一下什麼是代理對象內嵌調用,指的是一個代理方法調用了同類的另外一個代理方法。首先在這兒我要聲明事務直接的嵌套調用除外,至於爲何,是它已經將信息保存在線程級別了,是否是又點兒抽象,感受吃力,能夠看看我前面關於事務的介紹。java

@Async和@Transactional共存

@Component
    public class AsyncWithTransactional {
        
        @Async
        @Transactional
        public void test() {
            
        }
    }

這樣一段代碼會發生什麼?熟悉的人都會感受疑惑,都有效果麼?誰先被代理加強?spring

自動代理建立器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已是代理對象了,此時它會沿用這個代理,只須要把切面添加進去便可~異步

方法調用順序影響

想必你們都知道一點就是同類的方法調用只有入口方法被代理纔會被加強,這是因爲源碼級別只處理入口方法調用,是你的話你也這樣設計,否則方法棧那麼深,你管得了那麼多嗎?既然知道了這個緣由,那麼咱們接下來在看一下後面的列子。async

沿用代理對象

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)ide

咦,是否是咱們能夠從容器中獲取代理對象呢?沒有錯,從容器獲取代理對象也是一種沿用代理對象來調用方法鏈的手段,可是你會用麼?依賴於代理的具體實現而書寫代碼,這樣移植性會很是差的。post

揭祕@EnableAspectJAutoProxy(exposeProxy = true)

Spring內建的類且都是代理類的處理類:CglibAopProxyJdkDynamicAopProxy二者很相似,在處理這個邏輯上。因此此處只以JdkDynamicAopProxy做爲表明進行說明便可。this

咱們知道在執行代理對象的目標方法的時候,都會交給InvocationHandler處理,所以作事情的在invoke()方法裏:spa

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);
            }
        }

何時使用的呢?
file

AopContext.setCurrentProxy(@Nullable Object proxy)CglibAopProxyJdkDynamicAopProxy代理都有使用。

案例分析

@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()不能使用在非代理對象所在方法體內
相關文章
相關標籤/搜索