原創作品,可以,但是請標註出處地址:www.cnblogs.com/V1haoge/p/9476550.html
一、提出問題
不知道你是否遇到過這樣的情況,在ssm架構中開發web引用,或者使用springboot開發應用,當我們調用一個帶有@Transactional註解的方法執行某項事務操作的時候,有時候會發現事務是不生效的。
你是否考慮過這是為什麼,又該如何來修複事務呢?
二、分析問題
要想弄明白事務不生效的原因,我們首先要弄明白Spring中事務的實現原理,而Spring中的聲明式事務是使用AOP來實現的。
Spring中AOP又是依靠什麼實現的呢?動態代理,在Spring中使用的兩種動態代理,一種是java原生提供的JDK動態代理,另一種是第三方提供的CGLIB動態代理,前者基於介面實現,後者基於類實現,明顯後者的適用範圍更加廣泛,但是原生的JDK動態代理卻是速度要快很多,兩者各有特色。
動態代理的目的就是在應用運行時即時組建代理程式類,這樣我們就能在已有實現的基礎上對其進行增強,這其實也就是AOP的目的所在,增強類的功能。
動態代理產生的代理類擁有原生類的所有公有方法,針對指定方法的調用會轉移到代理類的同名方法之上,而在這個方法之內會在調用原生類的同名方法之外進行一些其他的操作,比如日誌記錄,比如安全檢查,比如事務操作等。
當我們在Controller層直接調用service層的一個帶有事務註解的方法時,就會執行以上步驟:組建代理程式類,調用代理類的同名方法,由代理類實現事務功能,再調用原生類的方法進行邏輯執行。
上面這種情況是沒有問題的,有問題的是我們在service層內部的方法調用本類中的帶有事務註解的方法時,該事務註解將失效,我們的調用方式無非就是直接調用或者用this調用,這兩種情況效果其實是一樣的,都是用當前執行個體調用。
結合之前的AOP和動態代理的介紹,我們很容易就能理解這裡事務失效的原因:那就是我們調用目標事務方法的時候直接調用的原生的方法,而沒有調用代理類中的代理方法,也就是說,我們沒有調用進行了事務增強方法,如此一來事務當然會失效了。
這麼來說,我們需要調用代理類中增強之後的代理方法,才能使事務生效。
三、解決問題
那麼我們要如何來修複呢?其實很簡單,只要我們不使用this調用即可。this代表的是當前執行個體,在spring中一般就是單例執行個體,自己調用自己的方法,事務註解等於擺設。如果我們更改調用方式,在當前類中注入自身單例執行個體,使用注入的執行個體來調用該方法,即可使事務生效。
為什麼呢?一般我們的SSM架構中的Service層都是有介面和實作類別的,既然存在介面,那麼這裡使用的必然是JDK動態代理來組建代理程式類。當我們將當前類的單例執行個體注入到自身之後,使用這個注入的執行個體來調用介面中的方法時,如果存在@Transactional之類的AOP增強註解存在,那麼就是組建代理程式類來實現功能增強。(在Springboot中開發的時候我們習慣去掉介面開發,那麼代理類就是使用CGLIB動態代理產生的)。
這樣也就要求我們的事務方法需要先在介面中聲明,然後在實作類別中實現邏輯,並添加事務註解。
這種方式適用於解決在Service中調用Service中的事務方法時事務失效的問題。這麼想想之前從Controller調用Service的時候也是通過注入的Service單例執行個體來調用的,這也側面證明我們提供的方法時有效。
四、問題引申
4.1 引申問題:循環相依性
至於由此引發的另一個問題:當我們在當前類中注入當前類的執行個體後,在建立這個類的執行個體的時候是需要注入這個類的執行個體的,但是這時候這個類有沒有建立完成,這該怎麼辦呢???
這就是Spring中著名的循環相依性問題。
更明顯的範例是在A中依賴B,B中又依賴A的情況,依賴相互彼此,那麼會不會導致兩個執行個體都建立失敗呢?
4.2 循環相依性的解決方案
有必要簡單說下Spring中針對這個問題的解決方案。為什麼是簡單介紹呢,因為我也只是簡單理解,但是這種簡單理解更加適用於不明白的朋友,不至於一來就懵逼。
我們都知道在Spring中Bean有多種生命週期範圍,主要就是單例和原型(當然還有request、Session等範圍),單例表示在整個應用上下文中只會存在一個Bean執行個體,而原型正好相反,可以存在多個Bean執行個體,每次調用getBean的時候都會建立一個新的bean執行個體。
我們要強調,在Spring中原型範圍的Bean執行個體如果發生循環相依性,只有一種下場:拋異常。
而針對單例bean,Spring內部提供了一種有效提前暴露的機制解決了循環相依性的問題。當然這裡僅僅解決的是使用setter方式實現依賴注入的情況,如果是使用構造器依賴注入的情況還是那種下場:拋異常。
拋異常代表,Spring無能力解決此問題,程式出錯。
為什麼呢?難道Spring不想解決嗎?肯定不是,而是無能為力罷了。
我們先簡單瞭解下setter方式實現依賴注入的單例Bean的循環相依性的解決方案:
先介紹下Spring中的那幾個緩衝池:
singletonObjects:單例緩衝池,用於儲存建立完成的單例Bean,是Map,凡是建立完畢的Bean執行個體全部儲存在該緩衝池中,不存在循環相依性的Bean會直接在建立完之後儲存到該緩衝中,而存在循環相依性的bean則會在其建立完成後由earlySingletonObjects轉移到此緩衝中。
singletonFactories:單例工廠緩衝池,用於儲存提前暴露的ObjectFactory,是Map
earlySingletonObjects:早期單例緩衝池,用於儲存尚未建立完成的用於早期暴露的單例Bean,是Map,它與singletonObjects是互斥的,就是不可能同時儲存於兩者之中,只能擇一而存,儲存在該緩衝池中的是尚未完成建立,而被注入到其他Bean中的Bean執行個體,可以說該緩衝就是一個中間緩衝(或者叫過程緩衝),只在當將該BeanName對應的原生Bean(處於建立中池)注入到另一個bean執行個體中後,將其添加到該緩衝中,這個緩衝中儲存的永遠是半成品的bean執行個體,當Bean執行個體最終完成建立後會從此緩衝中移除,轉移到singletonObjects緩衝中儲存。
registeredSingletons:登入的單例緩衝池,用於儲存已完成建立的Bean執行個體的beanName,是Set(此緩衝未涉及)
singletonsCurrentlyInCreation:建立中池,儲存處於建立中的單例bean的BeanName,是Set,在這個bean執行個體開始建立時添加到池中,而來Bean執行個體建立完成之後從池中移除。
當存在循環相依性的情況時,比如之前的情況:A依賴B,B又依賴A的情況,這種情況下,首先要建立A執行個體,將其beanName添加到singletonsCurrentlyInCreation池,然後調用A的構造器建立A的原生執行個體,並將其ObjectFactory添加到singletonFactories緩衝中,然後處理依賴注入(B執行個體),發現B執行個體不存在且也不在singletonsCurrentlyInCreation池中,表示Bean執行個體尚未進行建立,那麼下一步開始建立B執行個體,將其beanName添加到singletonsCurrentlyInCreation池,然後調用B的構造器建立A的原生執行個體,並將其ObjectFactory添加到singletonFactories緩衝中,再然後處理依賴注入(A執行個體),發現A執行個體尚未建立完成,但在singletonsCurrentlyInCreation池中發現了A執行個體的beanName,說明A執行個體正處於建立中,這時表示出現循環相依性,Spring會將singletonFactories緩衝中擷取對應A的beanName的ObjectFactory中getObject方法返回的Bean執行個體注入到B中,來完成B執行個體的建立步驟,同時也會將A的Bean執行個體添加到earlySingletonObjects緩衝中,表示A執行個體是一個提前暴露的Bean執行個體,B執行個體建立完畢之後需要將B的原生執行個體從singletonFactories緩衝中移除,並將完整執行個體添加到SingletonObjects緩衝中(當然earlySingletonObjects中也不能存在),並且將其beanName從singletonsCurrentlyInCreation池中移除(表示B執行個體完全建立完畢)。然後將B執行個體注入到A執行個體中來完成A執行個體的建立,最後同樣將A的原生執行個體從earlySingletonObjects中移除,完整執行個體添加到SingletonObjects中,並將A的beanName從建立中池中移除。到此完成A和B兩個單例執行個體的建立。
瞭解了上面所述的解決方案之後,我們可以明白針對構造器實現依賴注入的Bean發生循環相依性的情況下為什麼無法解決。那就是因為,之前提前暴露的前提是建立好原生的Bean執行個體,原聲的Bean執行個體就是依靠構造器建立的,如果在構造器建立Bean的時候就需要注入依賴,而依賴又正處於建立中的話,由於無法暴露ObjectFactory,而無法解決循環相依性問題。
另外原型bean的情況,Spring根本就不會對原型的Bean添加緩衝,因為添加緩衝的目的是為了保證單例Bean的唯一性,但是對於原型,就不能緩衝了,如果從緩衝擷取的Bean執行個體,那還是原型模式嗎?不存在緩衝當然也就無法實現上面描述的那一系列操作,也就無法解決循環相依性的問題了。
五、總結
Spring中的事務問題歸結為注入問題,循環相依性問題也是注入問題,有關注入的問題以後再討論。
Spring之中所有的增強都是依靠AOP實現的,而AOP又是依靠動態代理實現的,JDK的動態代理依靠反射技術實現,而CGLIB動態代理依靠位元組碼技術實現。