Spring基礎系列-Spring事務不生效的問題與迴圈依賴問題
原創作品,可以轉載,但是請標註出處地址:https://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動態代理依靠位元組碼技術實現。
相關文章
- 面試題:Spring 的迴圈依賴問題面試題Spring
- 【Spring系列】- Spring迴圈依賴Spring
- Spring IOC原始碼深度剖析:Spring IoC迴圈依賴問題Spring原始碼
- Spring 迴圈依賴的三種方式(三級快取解決Set迴圈依賴問題)Spring快取
- Spring5.0原始碼學習系列之淺談迴圈依賴問題Spring原始碼
- DDD領域層與基礎設施層迴圈依賴問題
- c++類迴圈依賴的問題C++
- Spring迴圈依賴Spring
- 爛大街的 Spring 迴圈依賴問題,你覺得自己會了嗎Spring
- Spring IoC - 迴圈依賴Spring
- Spring中的迴圈依賴Spring
- 【spring原始碼系列】之【Bean的迴圈依賴】Spring原始碼Bean
- 【spring】迴圈依賴 Java Vs SpringSpringJava
- 3.1 spring5原始碼系列--迴圈依賴 之 手寫程式碼模擬spring迴圈依賴Spring原始碼
- Spring 中 bean 的迴圈依賴SpringBean
- 【Spring】快速理解迴圈依賴Spring
- spring解決迴圈依賴Spring
- Spring迴圈依賴+案例解析Spring
- 再探迴圈依賴 → Spring 是如何判定原型迴圈依賴和構造方法迴圈依賴的?Spring原型構造方法
- Spring原始碼分析(三)手寫簡單的IOC容器和解決迴圈依賴問題Spring原始碼
- Spring如何解決迴圈依賴?Spring
- 【Spring】Spring中的迴圈依賴及解決Spring
- Spring原始碼分析:Spring的迴圈依賴分析Spring原始碼
- Java Web系列:Spring依賴注入基礎JavaWebSpring依賴注入
- 高頻面試題:一張圖徹底搞懂Spring迴圈依賴面試題Spring
- 定時任務裡面事務不生效問題
- 依賴管理利器---Spring IO Platform解決Spring組合依賴問題實戰SpringPlatform
- 幫助你更好的理解Spring迴圈依賴Spring
- spring是如何解決迴圈依賴的?Spring
- Spring是如何解決迴圈依賴的Spring
- Spring中如何解決迴圈依賴Spring
- 3.2spring原始碼系列----迴圈依賴原始碼分析Spring原始碼
- 3.4 spring5原始碼系列--迴圈依賴的設計思想Spring原始碼
- spring: 我是如何解決迴圈依賴的?Spring
- 關於我對Spring迴圈依賴的思考Spring
- Spring 是怎麼處理迴圈依賴的?Spring
- Spring原始碼分析之IOC迴圈依賴Spring原始碼
- 一文詳解spring迴圈依賴Spring