javascript
spring生命周期七个过程_Spring杂文(三)Spring循环引用
眾所周知spring在默認單例的情況下是支持循環引用的
Appconfig.java類的代碼
@Configurable @ComponentScan("com.sadow") public class Appconfig { }X.java類的代碼
@Component public class X {@AutowiredY y;public X(){System.out.println("X create");} }Y.java類的代碼
@Component public class Y {@AutowiredX x;public Y(){System.out.println("Y create");} }這兩個類非常簡單,就是相互引用了對方,也就是我們常常的說的循環依賴,spring是允許這樣的循環依賴(前提是單例的情況下的,非構造方法注入的情況下)
運行這段代碼的結果下圖
上面代碼從容器中能正常獲取到Xbean,說明循環依賴成功。但是spring的循環依賴其實是可以關閉的,spring提供了api來關閉循環依賴的功能。當然你也可以修改spring源碼來關閉這個功能,這里我為了提高逼格,就修改一下spring的源碼來關閉這個功能,老話說要想高明就得裝逼。
下圖是我修改spring源碼運行的結果
我在AnnotationConfigApplicationContext的構造方法中加了一行setAllowCircularReferences(false);結果代碼異常,循環依賴失敗
那么為什么setAllowCircularReferences(false);會關閉循環依賴呢?首要明白spring的循環依賴是怎么做到的呢?spring源碼當中是如何處理循環依賴的? 分析一下所謂的循環依賴其實無非就是屬性注入,或者就是大家常常說的自動注入, 故而搞明白循環依賴就需要去研究spring自動注入的源碼;spring的屬性注入屬于spring bean的生命周期一部分;怎么理解spring bean的生命周期呢?注意我這里并不打算對bean的生命周期大書特書,只是需要讀者理解生命周期的概念,細節以后在計較;
要理解bean的生命周期首先記住兩個概念
請讀者一定記住兩個概念——spring bean(一下簡稱bean)和對象;
- 1、spring bean——受spring容器管理的對象,可能經過了完整的spring bean生命周期(為什么是可能?難道還有bean是沒有經過bean生命周期的?答案是有的,具體我們前面文章分析了),最終存在spring容器當中;一個bean一定是個對象
- 2、對象——任何符合java語法規則實例化出來的對象,但是一個對象并不一定是spring bean;
所謂的bean的生命周期就是磁盤上的類通過spring掃描,然后實例化,跟著初始化,繼而放到容器當中的過程
上圖就是spring容器初始化bean的大概過程(至于詳細的過程,前面文章介紹了);
文字總結一下:
- 1:實例化一個ApplicationContext的對象;
- 2:調用bean工廠后置處理器完成掃描;
- 3:循環解析掃描出來的類信息;
- 4:實例化一個BeanDefinition對象來存儲解析出來的信息;
- 5:把實例化好的beanDefinition對象put到beanDefinitionMap當中緩存起來,以便后面實例化bean;
- 6:再次調用bean工廠后置處理器;
- 7:當然spring還會干很多事情,比如國際化,比如注冊BeanPostProcessor等等,如果我們只關心如何實例化一個bean的話那么這一步就是spring調用finishBeanFactoryInitialization方法來實例化單例的bean,實例化之前spring要做驗證,需要遍歷所有掃描出來的類,依次判斷這個bean是否Lazy,是否prototype,是否abstract等等;
- 8:如果驗證完成spring在實例化一個bean之前需要推斷構造方法,因為spring實例化對象是通過構造方法反射,故而需要知道用哪個構造方法;
- 9:推斷完構造方法之后spring調用構造方法反射實例化一個對象;注意我這里說的是對象、對象、對象;這個時候對象已經實例化出來了,但是并不是一個完整的bean,最簡單的體現是這個時候實例化出來的對象屬性是沒有注入,所以不是一個完整的bean;
- 10:spring處理合并后的beanDefinition(合并?是spring當中非常重要的一塊內容,后面的文章我會分析);
- 11:判斷是否支持循環依賴,如果支持則提前把一個工廠存入singletonFactories——map;
- 12:判斷是否需要完成屬性注入
- 13:如果需要完成屬性注入,則開始注入屬性
- 14:判斷bean的類型回調Aware接口
- 15:調用生命周期回調方法
- 16:如果需要代理則完成代理
- 17:put到單例池——bean完成——存在spring容器當中
- 用一個例子來證明上面的步驟
Z.java的源碼
@Component public class Z implements ApplicationContextAware {@AutowiredX x; //注入X//構造方法public Z() {System.out.println("Z create");}//生命周期初始化回調方法@PostConstructpublic void zinit() {System.out.println("call z lifecycle callback");}//ApplicationContextAware 回調方法@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {System.out.println("call aware callback");}}來看看Z的生命周期,注意下圖當中的字幕,會和上面的17個步驟一一對應
第一步到第六步,請自行對應
接下來我們通過各種圖片分析一下springbean的生命周期,讀者只需要看圖搞明白流程,至于圖中涉及的源碼,分析完流程之后再來解釋;
在研究其他步驟之前,首先了解spring大概在什么時候實例化bean的
上圖可以知道spring在AbstractApplicationContext#finishBeanFactoryInitialization方法中完成了bean的實例化。這點需要記住
然后通過圖片來說明一下第7步
接下來spring需要推斷構造方法,然后通過推斷出來的構造方法反射實例化對象,也就是上面說的第8步和第9步
當然他有可能推斷不出來構造方法——關于這塊知識我前面更新文章來說明
上圖說明spring是通過createBeanInstance(beanName, mbd, args);完成了推斷構造方法和實例化的事情那么接下來便要執行第10步處理合并后的beanDefinition對象,這一塊內容特別多。
仔細看上圖,其實這個時候雖然Z被實例化出來了,但是并沒有完成屬性的注入;其中的X屬性為null,而且里面的Aware接口的方法也沒有調用,再就是@PostConstruct方法也沒有調用,再一次說明他不是一個完整的bean,這里我們只能說z是個對象;
繼而applyMergedBeanDefinitionPostProcessors方法就是用來處理合并后的beanDefinition對象;
跟著第11步,判斷是否支持循環依賴,如果支持則提前暴露一個工廠對象,注意是工廠對象
第12步,spring會判斷是否需要完成屬性注入(spring默認是需要的,但是程序員可以擴展spring,根據情況是否需要完成屬性注入);如果需要則spring完成13步——屬性注入,也就是所謂的自動注入;
第14、15、16步
下面介紹一下spring創建一個bean的流程,通過源碼結合在idea當中debug截圖來說明
1、main方法,初始化spring容器,在初始化容器之后默認的單例bean已經實例化完成了,也就是說spring的默認單例bean創建流程、和所謂自動注入的功能都在容器初始化的過程中。故而我們需要研究這個容器初始化過程、在哪里體現了自動注入;
進入AnnotationConfigApplicationContext類的構造方法
2、在構造方法中調用了refresh方法,查看refresh方法
3、refresh方法當中調用finishBeanFactoryInitialization方法來實例化所有掃描出來的類
4、finishBeanFactoryInitialization方法當中調用preInstantiateSingletons初始化掃描出來的類
5、preInstantiateSingletons方法經過一系列判斷之后會調用getBean方法去實例化掃描出來的類
6、getBean方法就是個空殼方法,調用了doGetBean方法
doGetBean方法內容有點多,這個方法非常重要,不僅僅針對循環依賴,
甚至整個spring bean生命周期中這個方法也有著舉足輕重的地位,讀者可以認真看看我的分析。需要說明的是我為了更好的說清楚這個方法,我把代碼放到文章里面進行分析;但是刪除了一些無用的代碼;比如日志的記錄這些無關緊要的代碼。下面重點說這個doGetBean方法
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {//這個方法非常重要,但是和今天要分析的循環依賴沒什么很大的關系//讀者可以簡單的認為就是對beanName做一個校驗特殊字符串的功能//我會在前面更新的文章重點討論過這個方法//transformedBeanName(name)這里的name就是bean的名字final String beanName = transformedBeanName(name);//定義了一個對象,用來存將來返回出來的beanObject bean;// Eagerly check singleton cache for manu ally registered singletons./*** 注意這是第一次調用getSingleton方法,下面spring還會調用一次* 但是兩次調用的不是同一個方法;屬于方法重載* 第一次 getSingleton(beanName) 也是循環依賴最重要的方法* 關于getSingleton(beanName)具體代碼分析可以參考前面的分析* 這里給出這個方法的總結* 首先spring會去單例池去根據名字獲取這個bean,單例池就是一個map* 如果對象被創建了則直接從map中拿出來并且返回* 但是問題來了,為什么spring在創建一個bean的時候會去獲取一次呢?* 因為作為代碼的書寫者肯定知道這個bean這個時候沒有創建?為什么需要get一次呢?* 關于這個問題其實原因比較復雜,需要讀者對spring源碼設計比較精通* 我不準備來針對這個原因大書特書,稍微解釋一下吧* 我們可以分析doGetBean這個方法,顧名思義其實是用來獲取bean的* 為什么創建bean會調用這個doGetBean方法呢?難道是因為spring作者疏忽,獲取亂起名字* 顯然不是,spring的源碼以其書寫優秀、命名優秀而著名,那么怎么解釋這個方法呢?* 其實很簡單doGetBean這個方法不僅僅在創建bean的時候會被調用,在getBean的時候也會調用* 他是創建bean和getBean通用的方法。但是這只是解釋了這個方法的名字意義* 并么有解釋這個方法為什么會在創建bean的時候調用* 我前面已經說過原因很復雜,和本文有關的就是因為循環引用* 由于循環引用需要在創建bean的過程中去獲取被引用的那個類* 而被引用的這個類如果沒有創建,則會調用createBean來創建這個bean* 在創建這個被引用的bean的過程中會判斷這個bean的對象有沒有實例化* bean的對象?什么意思呢?* 為了方便閱讀,請讀者一定記住兩個概念;什么是bean,什么是對象* 我認為一個對象和bean是有區別的* 對象:只要類被實例化就可以稱為對象* bean:首先得是一個對象,然后這個對象需要經歷一系列的bean生命周期* 最后把這個對象put到單例池才能算一個bean* 讀者千萬注意,下文中如果寫到bean和寫到對象不是隨意寫的* 是和這里的解釋有關系的;重點一定要注意;一定;一定;* 簡而言之就是spring先new一個對象,繼而對這個對象進行生命周期回調* 接著對這個對象進行屬性填充,也是大家說的自動注入* 然后在進行AOP判斷等等;這一些操作簡稱----spring生命周期* 所以一個bean是一個經歷了spring周期的對象,和一個對象有區別* 再回到前面說的循環引用,首先spring掃描到一個需要被實例化的類A* 于是spring就去創建A;A=new A-a;new A的過程會調用getBean("a"));* 所謂的getBean方法--核心也就是現在寫注釋的這個getSingleton(beanName)* 這個時候get出來肯定為空?為什么是空呢?我寫這么多注釋就是為了解釋這個問題?* 可能有的讀者會認為getBean就是去容器中獲取,所以肯定為空,其實不然,接著往下看* 如果getA等于空;spring就會實例化A;也就是上面的new A* 但是在實例化A的時候會再次調用一下 * getSingleton(String beanName, ObjectFactory<?> singletonFactory)* 上面說過現在寫的注釋給getSingleton(beanName)* 也即是第一次調用getSingleton(beanName)* 實例化一共會調用兩次getSingleton方法;但是是重載* 第二次調用getSingleton方法的時候spring會在一個set集合當中記錄一下這個類正在被創建* 這個一定要記住,在調用完成第一次getSingleton完成之后* spring判讀這個類沒有創建,然后調用第二次getSingleton* 在第二次getSingleton里面記錄了一下自己已經開始實例化這個類* 這是循環依賴做的最牛逼的地方,兩次getSingleton的調用* 也是回答面試時候關于循環依賴必須要回答道的地方;* 需要說明的spring實例化一個對象底層用的是反射;* spring實例化一個對象的過程非常復雜,需要推斷構造方法等等;* 這里先不討論這個過程,以后有機會更新一下* 讀者可以理解spring直接通過new關鍵字來實例化一個對象* 但是這個時候對象a僅僅是一個對象,還不是一個完整的bean* 接著讓這個對象去完成spring的bean的生命周期* 過程中spring會判斷容器是否允許循環引用,判斷循環引用的代碼下面會分析* 前面說過spring默認是支持循環引用的,后面解析這個判斷的源碼也是spring默認支持循環引用的證據* 如果允許循環依賴,spring會把這個對象(還不是bean)臨時存起來,放到一個map當中* 注意這個map和單例池是兩個map,在spring源碼中單例池的map叫做 singletonObjects* 而這個存放臨時對象的map叫做singletonFactories* 當然spring還有一個存放臨時對象的map叫做earlySingletonObjects* 所以一共是三個map,有些博客或者書籍人也叫做三級緩存* 為什么需要三個map呢?先來了解這三個map到底都緩存了什么* 第一個map singletonObjects 存放的單例的bean* 第二個map singletonFactories 存放的臨時對象(沒有完整springBean生命周期的對象)* 第三個map earlySingletonObjects 存放的臨時對象(沒有完整springBean生命周期的對象)* 為了讓大家不懵逼這里吧第二個和第三個map功能寫成了一模一樣* 其實第二個和第三個map會有不一樣的地方,但這里不方便展開講,下文會分析* 讀者姑且認為這兩個map是一樣的* 第一個map主要為了直接緩存創建好的bean;方便程序員去getBean;很好理解* 第二個和第三個主要為了循環引用;為什么為了方便循環引用,接著往下看* 把對象a緩存到第二個map之后,會接著完善生命周期;* 當然spring bean的生命周期很有很多步驟;本文先不詳細討論;* 前面的文章我更新了;* 當進行到對象a的屬性填充的這一周期的時候,發覺a依賴了一個B類* 所以spring就會去判斷這個B類到底有沒有bean在容器當中* 這里的判斷就是從第一個map即單例池當中去拿一個bean* 后面我會通過源碼來證明是從第一個map中拿一個bean的* 假設沒有,那么spring會先去調用createBean創建這個bean* 于是又回到和創建A一樣的流程,在創建B的時候同樣也會去getBean("B");* getBean核心也就是現在寫注釋的這個getSingleton(beanName)方法* 下面我重申一下:因為是重點* 這個時候get出來肯定為空?為什么是空呢?我寫這么多注釋就是為了解釋這個問題?* 可能有的讀者會認為getBean就是去容器中獲取;* 所以肯定為空,其實不然,接著往下看;* 第一次調用完getSingleton完成之后會調用第二次getSingleton* 第二次調用getSingleton同樣會在set集合當中去記錄B正在被創建* 請記住這個時候 set集合至少有兩個記錄了 A和B;* 如果為空就 b=new B();創建一個b對象;* 再次說明一下關于實例化一個對象,spring做的很復雜,下次討論* 創建完B的對象之后,接著完善B的生命周期* 同樣也會判斷是否允許循環依賴,如果允許則把對象b存到第二個map當中;* 提醒一下這個時候第二個map當中至少有兩個對象了,a和b* 接著繼續生命周期;當進行到b對象的屬性填充的時候發覺b需要依賴A* 于是就去容器看看A有沒有創建,說白了就是從第一個map當中去找a* 有人會說不上A在前面創建了a嘛?注意那只是個對象,不是bean;* 還不在第一個map當中 對所以b判定A沒有創建,于是就是去創建A;* 那么又再次回到了原點了,創建A的過程中;首先調用getBean("a")* 上文說到getBean("a")的核心就是 getSingleton(beanName)* 上文也說了get出來a==null;但是這次卻不等于空了* 這次能拿出一個a對象;注意是對象不是bean* 為什么兩次不同?原因在于getSingleton(beanName)的源碼* getSingleton(beanName)首先從第一個map當中獲取bean* 這里就是獲取a;但是獲取不到;然后判斷a是不是等于空* 如果等于空則在判斷a是不是正在創建?什么叫做正在創建?* 就是判斷a那個set集合當中有沒有記錄A;* 如果這個集合當中包含了A則直接把a對象從map當中get出來并且返回* 所以這一次就不等于空了,于是B就可以自動注入這個a對象了* 這個時候a還只是對象,a這個對象里面依賴的B還沒有注入* 當b對象注入完成a之后,把B的周期走完,存到容器當中* 存完之后繼續返回,返回到a注入b哪里?* 因為b的創建時因為a需要注入b;于是去get b* 當b創建完成一個bean之后,返回b(b已經是一個bean了)* 需要說明的b是一個bean意味著b已經注入完成了a;這點上面已經說明了* 由于返回了一個b,故而a也能注入b了;* 接著a對象繼續完成生命周期,當走完之后a也在容器中了* 至此循環依賴搞定* 需要說明一下上文提到的正在創建這種說法并沒有官方支持* 是我老師的認為;各位讀者可以自行給他取名字* 老師是因為存放那些記錄的set集合的名字叫做singletonsCurrentlyInCreation* 顧名思義,當前正在創建的單例對象。。。。。* 還有上文提到的對象和bean的概念;也沒有官方支持* 也是為了讓讀者更好的理解spring源碼而提出的個人概念* 但是如果你覺得這種方法確實能讓你更好的理解spring源碼* 那么請姑且相信我老師對spring源碼的理解,假設10個人相信就會有100個人相信* 繼而會有更多人相信,就會成為官方說法,哈哈。* 以上是循環依賴的整個過程,其中getSingleton(beanName)* 這個方法的存在至關重要* 最后說明一下getSingleton(beanName)的源碼分析,下文會分析**/Object sharedInstance = getSingleton(beanName);/*** 如果sharedInstance不等于空直接返回* 當然這里沒有直接返回而是調用了getObjectForBeanInstance* 關于這方法以后解釋,讀者可以認為這里可以理解為* bean =sharedInstance; 然后方法最下面會返回bean* 什么時候不等于空?* 再容器初始化完成之后* 程序員直接調用getbean的時候不等于空* 什么時候等于空?* 上文已經解釋過了,創建對象的時候調用就會等于空*/ if (sharedInstance != null && args == null) {bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);}else {/*** 判斷這個類是不是在創建過程中* 上文說了,一個類是否在創建的過程中是第二次調用getSingleton中決定的* 這里還沒有執行到,如果就在創建過程中則出異常* **///prototypesCurrentlyInCreation 需要聯系 getSingleton方法if (isPrototypeCurrentlyInCreation(beanName)) {throw new BeanCurrentlyInCreationException(beanName);}else{/*** 需要說明的刪了很多和本文無用的代碼* 意思就是源碼中執行到這個if的時候有很多其他代碼* 但是都是一些判斷,很本文需要討論的問題關聯不大* 這個if就是判斷當前需要實例化的類是不是單例的* spring默認都是單例的,故而一般都成立的* 接下來便是調用第二次 getSingleton* 第二次會把當前正在創建的類記錄到set集合* 然后反射創建這個實例,并且走完生命周期* 第二次調用getSingleton的源碼分析會在下文**/if (mbd.isSingleton()) {sharedInstance = getSingleton(beanName, () -> {try {//完成了目標對象的創建//如果需要代理,還完成了代理return createBean(beanName, mbd, args);}catch (BeansException ex) {// Explicitly remove instance from singleton cache: It might have been put there// eagerly by the creation process, to allow for circular reference resolution.// Also remove any beans that received a temporary reference to the bean.destroySingleton(beanName);throw ex;}});bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);}return (T) bean;第一次調用getSingleton的源碼分析 Object sharedInstance =
getSingleton(beanName);
//空殼方法 public Object getSingleton(String beanName) {//重點,一定要記住這里傳的是一個true,面試會考return getSingleton(beanName, true); }/** 上面說的true對應這里的第二個參數boolean allowEarlyReference 顧名思義 叫做允許循環引用,而spring在內部調用這個方法的時候傳的true 這也能說明spring默認是支持循環引用的,這也是需要講過面試官的 但是你不能只講這一點,后面我會總結,這里先記著這個true 這個allowEarlyReference也是支持spring默認支持循環引用的其中一個原因 **/ protected Object getSingleton(String beanName, boolean allowEarlyReference) {/**首先spring會去第一個map當中去獲取一個bean;說白了就是從容器中獲取說明我們如果在容器初始化后調用getBean其實就從map中去獲取一個bean假設是初始化A的時候那么這個時候肯定等于空,前文分析過這個map的意義**/Object singletonObject = this.singletonObjects.get(beanName);/**我們這里的場景是初始化對象A第一次調用這個方法這段代碼非常重要,首先從容器中拿,如果拿不到,再判斷這個對象是不是在set集合這里的set集合前文已經解釋過了,就是判斷a是不是正在創建假設現在a不在創建過程,那么直接返回一個空,第一次getSingleton返回**/if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {synchronized (this.singletonObjects) {singletonObject = this.earlySingletonObjects.get(beanName);if (singletonObject == null && allowEarlyReference) {ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);if (singletonFactory != null) {singletonObject = singletonFactory.getObject();this.earlySingletonObjects.put(beanName, singletonObject);this.singletonFactories.remove(beanName);}}}}return singletonObject第二次調用getSingleton sharedInstance = getSingleton(beanName, () ->
代碼我做了刪減,刪了一些本本文無關的代碼
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {synchronized (this.singletonObjects) {//首先也是從第一個map即容器中獲取//再次證明如果我們在容器初始化后調用getBean其實就是從map當中獲取一個bean//我們這里的場景是初始化對象A第一次調用這個方法//那么肯定為空Object singletonObject = this.singletonObjects.get(beanName);if (singletonObject == null) {/**注意這行代碼,就是A的名字添加到set集合當中也就是我說的標識A正在創建過程當中這個方法比較簡單我就不單獨分析了,直接在這里給出singletonsCurrentlyInCreation.add就是放到set集合當中protected void beforeSingletonCreation(String beanName) {if (!this.inCreationCheckExclusions.contains(beanName)&& !this.singletonsCurrentlyInCreation.add(beanName)) {throw new BeanCurrentlyInCreationException(beanName);}}**/beforeSingletonCreation(beanName);boolean newSingleton = false;try {//這里便是創建一個bean的入口了//spring會首先實例化一個對象,然后走生命周期//走生命周期的時候前面說過會判斷是否允許循環依賴//如果允許則會把創建出來的這個對象放到第二個map當中//然后接著走生命周期當他走到屬性填充的時候//會去get一下B,因為需要填充B,也就是大家認為的自動注入//這些代碼下文分析,如果走完了生命周期singletonObject = singletonFactory.getObject();newSingleton = true;}}return singletonObject;} }如果允許則會把創建出來的這個對象放到第二個map當中
AbstractAutowireCapableBeanFactory#doCreateBean()方法部分代碼
由于這個方法內容過去多,我刪減了一些無用代碼 上面說的 singletonObject =
singletonFactory.getObject();
會開始創建bean調用AbstractAutowireCapableBeanFactory#doCreateBean() 在創建bean;
下面分析這個方法
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)throws BeanCreationException {// Instantiate the bean.BeanWrapper instanceWrapper = null;if (mbd.isSingleton()) {//如果你bean指定需要通過factoryMethod來創建則會在這里被創建//如果讀者不知道上面factoryMethod那你就忽略這行代碼//你可以認為你的A是一個普通類,不會再這里創建instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);}if (instanceWrapper == null) {//這里就通過反射創建一個對象,注意是對象不是bean//這個createBeanInstance的方法過于復雜,本文不做分析//以后如果有更新再來分析這個代碼//讀者可以理解這里就是new了一個A對象instanceWrapper = createBeanInstance(beanName, mbd, args);}//得到new出來的A,為什么需要得到呢?因為Anew出來之后存到一個對象的屬性當中final Object bean = instanceWrapper.getWrappedInstance();//重點:面試會考//這里就是判斷是不是支持循環引用和是否單例以及bean是否在創建過程中//判斷循環引用的是&& this.allowCircularReferences//allowCircularReferences在spring源碼當中默認就是true// private boolean allowCircularReferences = true; 這是spring源碼中的定義//并且這個屬性上面spring寫了一行非常重要的注釋// Whether to automatically try to resolve circular references between beans// 讀者自行翻譯,這是支持spring默認循環引用最核心的證據//讀者一定要講給面試官,關于怎么講,我后面會總結boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&isSingletonCurrentlyInCreation(beanName));//如果是單例,并且正在創建,并且是沒有關閉循環引用則執行//所以spring原形是不支持循環引用的這是證據,但是其實可以解決//怎么解決原形的循環依賴,下次更新吧 if (earlySingletonExposure) {//這里就是這個創建出來的A 對象a 放到第二個map當中//注意這里addSingletonFactory就是往map當中put//需要說明的是他的value并不是一個a對象//而是一段表達式,但是包含了這個對象的//所以上文說的第二個map和第三個map的有點不同//第三個map是直接放的a對象(下文會講到第三個map的),//第二個放的是一個表達式包含了a對象//為什么需要放一個表達式?下文分析吧addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));}// Initialize the bean instance.Object exposedObject = bean;try {//填充屬性,也就是所謂的自動注入//這個代碼我同一張圖來說明populateBean(beanName, mbd, instanceWrapper);exposedObject = initializeBean(beanName, exposedObject, mbd);}return exposedObject;populateBean(beanName, mbd, instanceWrapper)截圖圖說明
當A執行到屬性填充的時候會調用AutowiredAnnotationBeanPostProcessor的postProcessProperties方法來完成填充或者叫做自動注入b
下圖有很多文字注釋,可以放大圖上的注釋
填充B的時候先從容器中獲取B,這個時候b沒有創建則等于空,然后看B是不是正在創建,這個時候B只是執行了第一次getSingleton故而不在第二個map當中,所以返回空,返回空之后會執行創建B的流程;執行第二遍調用getSingleton的時候會把b標識正在創建的過程中,也就是添加到那個set集合當中;下圖做說明
創建B的流程和創建A差不多,把B放到set集合,標識B正在創建,繼而實例化b對象,然后執行生命周期流程,把創建的這個b對象放到第二個map當中,這個時候map當中已經有了a,b兩個對象。
然后執行b對象的屬性填充或者叫自動注入時候發覺需要依賴a,于是重復上面的getbean步驟,調用getSingleton方法;只不過現在a對象已經可以獲取了故而把獲取出來的a對象、臨時對象注入給b對象,然后走完b的生命周期流程后返回b所表示bean,跟著把這個b所表示的bean注入給a對象,最后走完a對象的其他生命周期流程;循環依賴流程全部走完;但是好像沒有說到第三個map,第三個map到底充當了什么角色呢?
這個知識點非常的重要,關于這個知識不少書籍和博客都說錯了,這也是寫這篇文章的意義;我說每次讀spring源碼都不一樣的收獲,這次最大的收獲便是這里了;我們先來看一下代碼;
場景是這樣的,spring創建A,記住第一次創建A,過程中發覺需要依賴B,于是創建B,創建B的時候發覺需要依賴A,于是再一次創建–第二次創建A,下面代碼就是基于第二次創建的A來分析;第二次創建A的時候依然會調用getSingleton,先獲取一下a
protected Object getSingleton(String beanName, boolean allowEarlyReference) {//先從第一個map獲取a這個bean,也就是單例池獲取Object singletonObject = this.singletonObjects.get(beanName);if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {synchronized (this.singletonObjects) {//然后從第三個map當中獲取a這個對象singletonObject = this.earlySingletonObjects.get(beanName);//如果第三個map獲取不到a對象,再看是否允許了循環引用//而這里的allowEarlyReference是true//為什么是true,上文說了這個方法是spring自己調用的,他默認傳了trueif (singletonObject == null && allowEarlyReference) {//然后從第二個map中獲取一個表達式//這里要非常注意第二個map當中存的不是一個單純的對象//前面說了第二個map當中存的是一個表達式,你可以理解為存了一個工廠//或者理解存了一個方法,方法里面有個參數就是這個對象//安裝spring的命名來分析應該理解為一個工廠singletonFactory//一個能夠生成a對象的工廠//那么他為什么需要這么一個工廠//這里我先大概說一下,是為了通過工廠來改變這個對象//至于為什么要改變對象,下文我會分析//當然大部分情況下是不需要改變這個對象的//讀者先可以考慮不需要改變這個對象,//那么這個map里面存的工廠就生產就是這個原對象,那么和第三個map功能一樣ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);if (singletonFactory != null) {//調用表達式,說白了就是調用工廠的方法,然后改變對象//我們假設對象不需要改變的情況那么返回了原對象就是a//需要改變的情況我們下文再分享singletonObject = singletonFactory.getObject();//然后把這個對象放到第三個map當中this.earlySingletonObjects.put(beanName, singletonObject);//把這個對象、或者表達式、或者工廠從第二個map中移除this.singletonFactories.remove(beanName);//重點:面試會考---為什么要放到第三個?為什么要移除第二個?/**首先我們通過分析做一個總結:spring首先從第一個map中拿a這個bean拿不到,從第三個map當中拿a這個對象拿不到,從第二個map拿a這個對象或者工廠拿到之后放到第三個map,移除第二個map里面的表達式、或者工廠如果對象需要改變,當改變完成之后就把他放到第三個里面這里的情況是b需要a而進行的步驟,試想一下以后如果還有C需要依賴a就不需要重復第二個map的工作了,也就是改變對象的工作了。因為改變完成之后的a對象已經在第三個map中了。不知道讀者能不能懂我的意思如果對象不需要改變道理是一樣的,也同樣在第三個map取就是了;至于為什么需要移除第二個map里面的工廠、或者表達式就更好理解了他已經對a做完了改變,改變之后的對象已經在第三個map了,為了方便gc啊下面對為什么需要改變對象做分析*/}}}}return singletonObject; }為什么需要改變對象?那個表達式、或者說工廠主要干什么事呢? 那個工廠、或者表達式主要是調用了下面這個方法
//這個方法內容比較少,但是很復雜,因為是對后置處理器的調用 //關于后置處理器其實要說話很多很多 //現在市面上可見的資料或者書籍對后置處理器的說法筆者一般都不茍同 //我老師在B站上傳過一個4個小時的視頻,其中對spring后置處理器做了詳細的分析 //也提出了一些自己的理解和主流理解不同的地方,有興趣同學可以去看看 //其實簡單說--這個方法作用主要是為了來處理aop的; //當然還有其他功能,但是一般的讀者最熟悉的就是aop //這里我說明一下,aop的原理或者流程有很多書籍說到過 //但是我親測了,現在市面可見的資料和書籍對aop的說法都不全 //很多資料提到aop是在spring bean的生命周期里面填充屬性之后的代理周期完成的 //而這個代理周期甚至是在執行生命周期回調方法之后的一個周期 //那么問題來了?什么叫spring生命周期回調方法周期呢? // 首先spring bean生命周期和spring生命周期回調方法周期是兩個概念 //spring生命周期回調方法是spring bean生命周期的一部分、或者說一個周期 //簡單理解就是spring bean的生命的某個過程會去執行spring的生命周期的回調方法 //比如你在某個bean的方法上面寫一個加@PostConstruct的方法(一般稱bean初始化方法) //那么這個方法會在spring實例化一個對象之后,填充屬性之后執行這個加注解的方法 //我這里叫做spring 生命周期回調方法的生命周期,不是我胡說,有官方文檔可以參考的 //在執行完spring生命周期回調方法的生命周期之后才會執行代理生命周期 //在代理這個生命周期當中如果有需要會完成aop的功能 //以上是現在主流的說法,也是一般書籍或者“某些大師”的說法 //但是在循環引用的時候就不一樣了,循環引用的情況下這個周期這里就完成了aop的代理 //這個周期嚴格意義上是在填充屬性之前(填充屬性也是一個生命周期階段) //填充屬性的周期甚至在生命周期回調方法之前,更在代理這個周期之前了 //簡單來說主流說法代理的生命周期比如在第8個周期或者第八步吧 //但是這里得出的結論,如果一個bean是循環引用的則代理的周期可能在第3步就完成了 //那么為什么需要在第三步就完成呢? //試想一下A、B兩個類,現在對A類做aop處理,也就是需要對A代理 //不考慮循環引用 spring 先實例化A,然后走生命周期確實在第8個周期完成的代理 //關于這個結論可以去看b站我講的spring aop源碼分析 //但是如果是循環依賴就會有問題 //比如spring 實例化A 然后發現需要注入B這個時候A還沒有走到8步 //還沒有完成代理,發覺需要注入B,便去創建B,創建B的時候 //發覺需要注入A,于是創建A,創建的過程中通過getSingleton //得到了a對象,注意是對象,一個沒有完成代理的對象 //然后把這個a注入給B?這樣做合適嗎?注入的a根本沒有aop功能;顯然不合適 //因為b中注入的a需要是一個代理對象 //而這個時候a存在第二個map中;不是一個代理對象; //于是我在第二個map中就不能單純的存一個對象,需要存一個工廠 //這個工廠在特殊的時候需要對a對象做改變,比如這里說的代理(需要aop功能的情況) //這也是三個map存在的必要性,不知道讀者能不能get到點protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {Object exposedObject = bean;if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {for (BeanPostProcessor bp : getBeanPostProcessors()) {if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);}}}return exposedObject總結關于循環引用,如何回答面試:
首先spring在單例的情況下是默認支持循環引用的(當然原形也有辦法,今天先不討論);在不做任何配置的情況下,兩個bean相互依賴是能初始化成功的;spring源碼中在創建bean的時候先創建這個bean的對象,創建對象完成之后通過判斷容器對象的allowCircularReferences屬性決定是否允許緩存這個臨時對象,如果能被緩存成功則通過緩存提前暴露這個臨時對象來完成循環依賴;而這個屬性默認為true,所以說spring默認支持循環依賴的,但是這個屬性spring提供了api讓程序員來修改,所以spring也提供了關閉循環引用的功能;再就是spring完成這個臨時對象的生命周期的過程中當執行到注入屬性或者自動裝配的周期時候會通過getSingleton方法去得到需要注入的b對象;而b對象這個時候肯定不存在故而會創建b對象創建b對象成功后繼續b對象的生命周期,當執行到b對象的自動注入周期時候會要求注入a對象;調用getSingleton;從map緩存中得到a的臨時對象(因為這個時候a在set集合中;這里可以展開講),而且獲取的時候也會判斷是否允許循環引用,但是判斷的這個值是通過參數傳進來的,也就是spring內部調用的,spring源碼當中寫死了為true,故而如果需要擴展spring、或者對spring二次開發的的時候程序員可以自定義這個值來實現自己的功能;不管放到緩存還是從緩存中取出這個臨時都需要判斷;而這兩次判斷spring源碼當中都是默認為true;這里也能再次說明spring默認是支持循環引用的;
然后面試中可以在說說兩次調用getSingleton的意義,正在創建的那個set集合有什么用;最后在說說你在看spring循環引用的時候得出的aop實例化過程的新發現;就比較完美了.
總結
以上是生活随笔為你收集整理的spring生命周期七个过程_Spring杂文(三)Spring循环引用的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 人工智能课程设计——植物识别专家系统
- 下一篇: linux的vi命令详解,linux v