前言
初夏時節, 時間: AM 7.30分左右, 空無一人的健身房裡,一個碩大的身體在跑步機上扭動著, 不一會頭上便揮汗如雨, 他嘴上還不時嘀咕著
"循環依賴,單例模式,Bean的定位加載註冊,原型模式...", 漸漸跑著跑著就變成了慢慢悠悠的走走歇歇,忽然間感覺肩膀好像被磚頭砸了一下,身後傳來一句 "你張大胖減肥是不可能減肥的,就是在健身房裡劃劃水才能維持的了生活 !" ,
大胖被拍的渾身一驚, 看了眼一身運動裝的 Mason, 急忙反駁道 "沒有調查就沒有發言權, 俺只是暫時休息下, 今天都已經跑了好幾個小時了呢,倒是 Mason 你像是來假健身的...",
Mason 笑笑說 "哎呀呀, 你這麼厲害呢,可惟獨肚子上的贅肉騙不了人啊,最近面試的怎麼樣?"
大胖 一臉愁容的說道: "最近招聘市場很給力,我也參加了不少面試,就是每次聊到 Spring 時被面試官三連追問 Spring是如何解決循環依賴, 而我對著個問題查了很多資料,但也就能回答個一知半解,然後就叫我回去等通知了..."
Mason 在跑步機上邊跑邊說 "Spring 解決循環依賴的這個場景, 其實也可以映射到生活中, 比如 你工作日睡過了又害怕遲到扣錢,就在 DD出行 App上選擇一個起始地打車, 同一時間來了兩輛 DD專車, 你也沒有check車牌就上車了, 司機也沒check你拉上就走,你一上車就聚精會神看起宅舞視頻, 結果到達別人的目的地發現上錯車,既遲到了又要付來回路費,那麼問題來了 你為什麼會上錯車呢? "
大胖 撓撓頭回答道: "因為睡過了怕遲到啊! 呸,不對, 因為要早起去打工!"
Mason 白了張大胖一眼說: "起不來是因為你天天熬夜,坐錯車是因為兩個原因: 1.沒有check車牌 2.DD專車不止一輛"
大胖 一臉懵X 的問道 "繞了半天, 那這上錯車和Spring創建Bean時的循環依賴又有什麼關係呢 ?"
Mason 一臉這孩子沒救了的表情回答道 "循環依賴的觸發條件就是, 你上了別人的DD專車, 而你打DD專車, 判斷是否循環依賴就需要 check車牌,如果要徹底根治循環依賴就必須讓世界上的 DD專車 只有一輛. Spring 中的循環依賴也是同理!"
見微知著
我們暫不討論 Spring 的循環依賴, 先看一道 LeetCode 題目 141. 環形鏈表 擴展: 在多線程環境下使用JDK1.7中的HashMap, 併發調用resize()時會出現環形鏈表,後再get()會導致CPU 100%, 那我們該如何去判斷環形鏈表呢?
給定一個鏈表,判斷鏈表中是否有環。
為了表示給定鏈表中的環,我們使用整數 pos 來表示鏈表尾連接到鏈表中的位置(索引從 0 開始)。 如果 pos 是 -1,則在該鏈表中沒有環。
示例 1:
<code>輸入:head = [3,2,0,-4], pos = 1 輸出:true 解釋:鏈表中有一個環,其尾部連接到第二個節點。/<code>
示例 2:
<code>輸入:head = [1,2], pos = 0 輸出:true 解釋:鏈表中有一個環,其尾部連接到第一個節點。/<code>
示例 3:
<code>輸入:head = [1], pos = -1 輸出:false 解釋:鏈表中沒有環。/<code>
那麼 Spring 中是用那種方式發現 循環依賴的呢 ? (文章結尾揭曉答案)
<code>// Definition for singly-linked list. public class ListNode { public int val; public ListNode next; public ListNode(int x) { val = x; } }/<code>
Set判重
<code> public boolean hasCycle_Set(ListNode head) { // 如果入參鏈表過短則不存在環 if (head == null || head.next == null) { return false; } HashSet set = new HashSet<>(); // 如果 遍歷到最後任然沒有發現環則不存在環 while (head.next != null){ // 將每一個遍歷過的元素存入,之後判重 if (set.contains(head)){ return true; } set.add(head); head = head.next; } return false; }/<code>
快慢指針判重
<code> public boolean hasCycle_QuickSlowPointer (ListNode head) { // 如果入參鏈表過短則不存在環 if (head == null || head.next == null) { return false; } ListNode slow = head; ListNode fast = head; // 如果 快指針遍歷到最後仍然沒有發現環則不存在環 while (fast.next != null && fast.next.next != null){ slow = slow.next; fast = fast.next.next; // 快指針每輪走兩步 慢指針每輪走一步 如果有環快慢指針最終就會相遇 if (slow == fast){ return true; } } return false; }/<code>
新整了個活, 今後長期維護的一個LeetCode題解庫,歡迎 Star
LeetCode 漸進式題解庫: 讓天下沒有難刷的題 (Java)
Spring 中常用的兩種 Bean DI 方式的循環依賴示例
Spring DI (Dependency Injection 依賴注入) 是指 Bean 被動接受其他 Bean的依賴注入而不自己主動去找, 簡而言之 Bean 不會從容器中查找它依賴的 Bean , 而是靠容器實例化 Bean 時由容器將它依賴的 Bean 注入, 此舉與Java 類實例化流程相反.
構造器 DI 示例代碼(基於Spring 5.1.6)
<code>package org.springframework.context.annotationX.circular.constructor; import org.springframework.stereotype.Component; /** * Spring Constructor DI 循環依賴 Bean1 Demo */ @Component public class Bean1ConstructorBean2Demo { private Bean2ConstructorBean1Demo bean2; public Bean1ConstructorBean2Demo(Bean2ConstructorBean1Demo bean2DependBean1Demo) { this.bean2 = bean2DependBean1Demo; } public void hello() { bean2.hello(); } } /<code>
<code>package org.springframework.context.annotationX.circular.constructor; import org.springframework.stereotype.Component; /** * Spring Constructor DI 循環依賴 Bean2 Demo */ @Component public class Bean2ConstructorBean1Demo { private Bean1ConstructorBean2Demo bean1; public Bean2ConstructorBean1Demo(Bean1ConstructorBean2Demo bean1DependBean2Demo1) { bean1 = bean1DependBean2Demo1; } public void hello() { System.out.println("Run Circular Dependency Success"); } } /<code>
註解自動裝配 DI 示例代碼
<code>package org.springframework.context.annotationX.circular.autowired; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; /** * Spring @Autowired DI 循環依賴 Bean1 Demo */ @Component public class Bean1AutowiredBean2Demo { @Autowired private Bean2AutowiredBean1Demo bean2; public void hello() { bean2.hello(); } }/<code>
<code>package org.springframework.context.annotationX.circular.autowired; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; /** * Spring @Autowired DI 循環依賴 Bean2 Demo */ @Component public class Bean2AutowiredBean1Demo { @Autowired private Bean1AutowiredBean2Demo bean1; public void hello(){ System.out.println("Run Circular Dependency Success"); } }/<code>
兩種 DI 方式的單元測試代碼
<code>package org.springframework.context; import org.junit.Test; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotationX.circular.autowired.Bean1AutowiredBean2Demo; import org.springframework.context.annotationX.circular.constructor.Bean1ConstructorBean2Demo; /** * Created by 以鬥爭求團結則團結存,以退讓求團結則團結亡 ! */ public class AnnotationCircularDependencyTestX { @Test public void diBeanByAutowired() throws Exception { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); context.scan("org.springframework.context.annotationX.circular.autowired"); context.refresh(); Bean1AutowiredBean2Demo bean1 = (Bean1AutowiredBean2Demo) context.getBean("bean1AutowiredBean2Demo"); bean1.hello(); } @Test public void diBeanByConstructor () throws Exception { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); context.scan("org.springframework.context.annotationX.circular.constructor"); context.refresh(); Bean1ConstructorBean2Demo bean1 = (Bean1ConstructorBean2Demo) context.getBean("bean1ConstructorBean2Demo"); bean1.hello(); } }/<code>
猜猜上面那種 DI 方式打印了 "Run Circular Dependency Success" 以及它解決循環依賴的方式 ?
答案: 註解自動裝配 DI 示例代碼 打印成功 而 構造器 DI 示例代碼因為循環依賴運行失敗 , 那這兩種方式有什麼區別呢 !
(構造器 DI 錯誤日誌)
<code> Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'bean2ConstructorBean1Demo' defined in file [D:\SpringFamily-SourceCodeStudy\Spring-Framework\spring-framework-5.1.6.REL EASE\spring-context\out\test\classes\org\springframework\context\annotationX\circular\constructor\Bean2Const ructorBean1Demo.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'bean1ConstructorBean2Demo': Requested bean is currently in creation: Is there an unresolvable circular reference? at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:769) at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:218) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1341) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1187) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:555) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:515) at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:320) at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222) at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:849) at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:877) ..../<code>
- 註解自動裝配 DI 方式 優點: 基於三層緩存不會發生循環依賴 自描述清晰 缺點: 對構建單元測試不友好
- 構造器 DI 方式 優點: 代碼結構明確,可讀性高 構建單元測試友好 非IOC容器環境可使用new實例化該類的對象。 缺點: 會發生循環依賴 當注入參數較多時,代碼臃腫。
對於循環依賴問題,Spring根據注入方式,採取不同的處理策略,如果依賴雙方都是使用屬性值注入或者Setter方法注入,則Spring可以自動解決循環依賴注入問題,Spring 程序可以成功啟動;如果依賴雙方是使用構造函數注入對方或者主Bean對象使用構造函數注入或循環注入的Bean都是原型模式 ,則Spring 無法解決循環依賴注入 ,Spring程序報循環依賴無法啟動。
註解 DI 方式 的運行成功原因
註解 DI 方式與 構造器 DI 方式 最終要達到的目的相同, 但Spring 對它倆的實現卻不太一樣, Spring-Context 在 AbstractApplicationContext.refresh() 方法完成 Bean 的關鍵生命週期 IOC, 實例化, DI 等, DI 具體是 IOC 完成後調用 finishBeanFactoryInitialization() 方法, 具體方法邏輯如下
- 能觸發依賴注入的條件有倆: 1. 第一次調用 getBean 方法時, 2. 懶加載被預實例化時, 此方法滿足了其中第一條.
- 1.根據 beanDefinitionMap 依次判斷 Bean 是否 Lazy, 是否 Prototype, 是否 Abstract 等等
- 2.接下來根據判斷結果 填充構造方法來反射 Bean 的從而實例化. 所以在此之前必須推斷Bean的構造方法.
- 3.反射實例化一個對象;注意我這裡說的是對象、對象、對象;不是並不是一個完整的bean,因為 對象屬性是沒有注入,所以不是一個完整的bean;
- 4.Spring 處理 合併後的 BeanDefinition.
- 5.判斷是否支持 循環依賴 如果支持則提前把 一個工廠存入 singletonFactories Map>
- 6.進行屬性注入
- 7.回調 Aware 接口, 生命週期回調方法, 是否需要代理
- 8.put 到 Spring容器
- 文章最後會帶大家詳細閱讀上述源碼.
成功原因:
<code>// 是否需要提前曝光,用來解決循環依賴時使用 boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName)); if (earlySingletonExposure) { if (logger.isTraceEnabled()) { logger.trace("Eagerly caching bean '" + beanName + "' to allow for resolving potential circular references"); } // 這裡是一個匿名內部類, 為了循環引用, 儘早持有對象的引用 // 解決循環依賴 第二個參數是回調接口,實現的功能是將切面動態織入 bean addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); } protected void addSingletonFactory(String beanName, ObjectFactory> singletonFactory) { Assert.notNull(singletonFactory, "Singleton factory must not be null"); synchronized (this.singletonObjects) { // 判斷 singletonObjects 不存在 beanName if (!this.singletonObjects.containsKey(beanName)) { // 註釋 5.4 放入 beanName -> beanFactory,到時在 getSingleton() 獲取單例時,可直接獲取創建對應 bean 的工廠,解決循環依賴 this.singletonFactories.put(beanName, singletonFactory); // 從提前曝光的緩存中移除,之前在 getSingleton() 放入的 this.earlySingletonObjects.remove(beanName); // 往註冊緩存中添加 beanName this.registeredSingletons.add(beanName); } } } /<code>
先來看 earlySingletonExposure 這個變量: 從字面意思理解就是需要提前曝光的單例。
有以下三個判斷條件:
- mbd 是否是單例
- 該容器是否允許循環依賴
- 判斷該 bean 是否在創建中。
如果這三個條件都滿足的話,就會執行 addSingletonFactory 操作。要想著,寫的代碼都有用處,所以接下來看下這個操作解決的什麼問題和在哪裡使用到吧
A 類中含有屬性 B,B 類中含有屬性 A,這兩個類在初始化的時候經歷了以下的步驟:
- 創建 Bean1AutowiredBean2Demo,先記錄對應的 beanName 然後將 Bean1AutowiredBean2Demo的創建工廠 beanFactoryA 放入緩存中
- 對 Bean1AutowiredBean2Demo的屬性填充方法 populateBean,檢查到依賴 Bean2AutowiredBean1Demo,緩存中沒有 Bean2AutowiredBean1Demo 的實例或者單例緩存,於是要去實例化 Bean2AutowiredBean1Demo。
- 開始實例化 Bean2AutowiredBean1Demo,經歷創建 Bean1AutowiredBean2Demo的過程,到了屬性填充方法,檢查到依賴了 Bean1AutowiredBean2Demo。
- 調用 getBean(Bean1AutowiredBean2Demo) 方法,在這個函數中,不是真正去實例化 Bean1AutowiredBean2Demo,而是先去檢測緩存中是否有已經創建好的對應的 bean,或者已經創建好的 beanFactory
- 檢測到 beanFactoryA 已經創建好了,而是直接調用 ObjectFactory 去創建 Bean1AutowiredBean2Demo
構造器 DI 方式 運行失敗的原因
構造方法DI 拋出異常前調用堆棧信息
失敗原因: 根據上述堆棧可以分析, autowireConstructor() 試圖通過構造方法反射 bean1ConstructorBean2Demo 實例時它必須先實例化 bean2ConstructorBean1Demo 然後依次循環, 走到 getSingleton() -> DefaultSingletonBeanRegistry.beforeSingletonCreation() 方法時就檢測出異常, 那麼為什麼這種方式沒有像 註解 DI 那樣解決問題呢 ?
- 註解DI方式 與 構造器DI方式最大的區別在與 AbstractAutowireCapableBeanFactory.createBeanInstance() 中的實例化策略不太一樣. 從各自定義SpringBean的源代碼上看, 構造器DI方式 需要申明當前類的構造器以及依賴的類, 而 註解DI方式則不需要 (默認空參)
- 構造器DI: return autowireConstructor(beanName, mbd, ctors, args); 使用容器的自動裝配特性, 調用匹配的構造方法進行實例化
- 註解DI: return instantiateBean(beanName, mbd); 使用默認的無參構造方法進行實例化
- 如上圖所示, 調用堆棧 在 createBeanInstance() 實例化方法中發生了循環引用, 並沒有執行到 populateBean()進行依賴注入. 為什麼會發生這一切?
在此之前建議閱讀一下 Spring官方對循環依賴的文檔
DI時 thorw BeanCurrentlyInCreationException 代碼片段
<code>// 構造方法DI getSingleton() 中 thorw BeanCurrentlyInCreationException public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry { // 當前 Bean 不存在可排除的 inCreationCheckExclusions && 當前 Bean 之前已存在於 則 thorw singletonsCurrentlyInCreation 中 protected void beforeSingletonCreation(String beanName) { if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) { throw new BeanCurrentlyInCreationException(beanName); } } protected void afterSingletonCreation(String beanName) { if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.remove(beanName)) { throw new IllegalStateException("Singleton '" + beanName + "' isn't currently in creation"); } } } /<code>
構造方法DI thorw 的觸發原因: 兩次 beforeSingletonCreation() 同一個Bean, 因為如果是沒有發生循環依賴的話接下來會執行 afterSingletonCreation(beanName) 清除本輪 singletonsCurrentlyInCreation.remove(beanName) 但在 beforeSingletonCreation--> 遞歸 autowireConstructor()
<code>// 原型BeanDI doGetBean 中 thorw BeanCurrentlyInCreationException public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory implements AutowireCapableBeanFactory { protected T doGetBean(final String name, @Nullable final Class requiredType, @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException { // 如果之前創建過相同的原型Bean 則 thorw if (isPrototypeCurrentlyInCreation(beanName)) { throw new BeanCurrentlyInCreationException(beanName); } /** 創建原型模式 Bean 的實例對象*/ if (mbd.isPrototype()) { // 原型模式 (Prototype) 每次都會創建一個新的對象 Object prototypeInstance = null; try { // 回調 beforePrototypeCreation() 方法, 默認的功能是註冊當前創建的原型對象 beforePrototypeCreation(beanName); // 創建指定 Bean 的對象實例 prototypeInstance = createBean(beanName, mbd, args); } finally { // 回調 afterPrototypeCreation() 方法, 默認的功能是告訴 IOC 容器 不再創建指定 Bean 的原型對象 afterPrototypeCreation(beanName); } bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd); } ... } } public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory { private final ThreadLocal prototypesCurrentlyInCreation = new NamedThreadLocal <>("Prototype beans currently in creation"); protected boolean isPrototypeCurrentlyInCreation(String beanName) { Object curVal = this.prototypesCurrentlyInCreation.get(); return (curVal != null && (curVal.equals(beanName) || (curVal instanceof Set && ((Set>) curVal).contains(beanName)))); } protected void beforePrototypeCreation(String beanName) { Object curVal = this.prototypesCurrentlyInCreation.get(); if (curVal == null) { this.prototypesCurrentlyInCreation.set(beanName); } else if (curVal instanceof String) { Set beanNameSet = new HashSet<>(2); beanNameSet.add((String) curVal); beanNameSet.add(beanName); this.prototypesCurrentlyInCreation.set(beanNameSet); } else { Set beanNameSet = (Set) curVal; beanNameSet.add(beanName); } } protected void afterPrototypeCreation(String beanName) { Object curVal = this.prototypesCurrentlyInCreation.get(); if (curVal instanceof String) { this.prototypesCurrentlyInCreation.remove(); } else if (curVal instanceof Set) { Set beanNameSet = (Set) curVal; beanNameSet.remove(beanName); if (beanNameSet.isEmpty()) { this.prototypesCurrentlyInCreation.remove(); } } } }/<code>
原型BeanDI thorw 的觸發原因: 兩次 beforePrototypeCreation() 同一個Bean, 因為如果是沒有發生循環依賴的話接下來會執行 afterPrototypeCreation(beanName) 清除本輪 prototypesCurrentlyInCreation.remove() 但在 beforePrototypeCreation--> 遞歸 doGetBean()
回溯幾個有意思的問題
Spring是如何發現循環依賴的?
巧妙的用了LeetCode[141]中 Set 解法 把Bean 的加載順序當作一個單向鏈表邊存入邊判重.
Spring的註解DI方式 是如何解決循環依賴的 ?
因為使用 註解DI 代碼風格上是沒有構造函數的, 在AbstractAutowireCapableBeanFactory.createBeanInstance() 走空參構造進行實例化, 所以不需要去像構造器DI 那樣去實例化別的類, 然後在 populateBean() 中進行屬性注入, 這時候已經完成實例化了要進行依賴注入了, 構造器DI方式就是在 實例化的時候翻車的呀, 具體怎麼進行循環依賴的屬性注入就靠 二 三級緩存咯.
- 一級緩存: singletonObjects 它是我們最熟悉的朋友,俗稱“單例池”“容器”,緩存創建完成單例Bean的地方, 也可以稱之為 Spring 容器.
- 二級緩存: singletonFactories 映射創建Bean的原始工廠
- 三級緩存: earlySingletonObjects 映射Bean的早期引用,也就是說在這個Map裡的Bean不是完整的,甚至還不能稱之為“Bean”,只是一個Instance.
千言萬語都在gif圖裡, 感謝作者vt 授權...
修復構造器 DI 引發的循環依賴的補丁有那幾種 ?
- 在主構造DI 方法上加上 @Lazy, Spring會動態代理創建代理類來解決
- 在發生循環依賴的注入主類上加上 @Autowired, 並記得刪除構造函數
- 實現 InitializingBean, ApplicationContextAware 接口, 在Spring 用 InitializingBean 時手動獲取容器注入.
- 更多
Spring Bean 是如何被創建的 ?
在學習 Spring 時你可以把它比作一家 糖果工廠 , 糖果工廠裡很多條夾心軟糖生產線, 其中最賺錢的兩條夾心軟糖生產線, 分別是A生產線 與X生產線, A生產線生產軟糖要必備一種叫@註解的糖漿原料, X 生產線則要必備另一種叫
兩種軟糖的初體驗
<code>import org.junit.Test; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class SoftSweetsTest { @Test public void A_ProductLine() { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); context.scan("org.springframework.context.softsweetsX"); context.refresh(); SoftSweetsBean bean = (SoftSweetsBean) context.getBean("softSweetsBean"); System.out.println("\n新鮮出爐的A軟糖 ~~~ \n 生產線名稱: " + bean.getProductionLineName() + "\n 生產日期: " + bean.getDateManufacture()); } @Test public void X_ProductLine() { // test-resources ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("org/springframework/beans/factory/xml/SoftSweetsX.xml"); SoftSweetsBean bean = (SoftSweetsBean) context.getBean("softSweetsBean"); System.out.println("\n新鮮出爐的X軟糖 ~~~ \n 生產線名稱: " + bean.getProductionLineName() + "\n 生產日期: " + bean.getDateManufacture()); } }/<code>
控制檯輸出結果
<code>新鮮出爐的X軟糖 ~~~ 生產線名稱: org.springframework.context.support.ClassPathXmlApplicationContext@525b461a 生產日期: 2020-05-28T13:57:23.738+08:00[Asia/Shanghai] 新鮮出爐的A軟糖 ~~~ 生產線名稱: org.springframework.context.annotation.AnnotationConfigApplicationContext@10db82ae 生產日期: 2020-05-28T13:57:24.784+08:00[Asia/Shanghai]/<code>
如果你對生產軟糖感興趣 來吧 -> SpringFamily-SourceCodeStudy
Bean 加載步驟
- IOC (Inversion of Control 控制反轉) 定位 (確定原料位置) 加載 (找到原料後提取為可註冊 BeanDefinition) 註冊 (將 BeanDefinition 校驗後註冊到 Map beanDefinitionMap)
- DI (Dependency Injection 依賴注入) 實例化 (反射 new 對象)ioc 依賴注入 (容器主動查找 bean 依賴)
IOC 控制反轉
兩條生產線的 定位與 加載 代碼
<code>// X 生產線 public class ClassPathXmlApplicationContext extends AbstractXmlApplicationContext { public ClassPathXmlApplicationContext(String configLocation) throws BeansException { this(new String[] {configLocation}, true, null); } public ClassPathXmlApplicationContext( String[] configLocations, boolean refresh, @Nullable ApplicationContext parent) throws BeansException { // 設置容器資源加載器 super(parent); /* 將配置的Bean信息為 Spring 封裝的 Resource */ setConfigLocations(configLocations); if (refresh) { refresh(); } } public abstract class AbstractXmlApplicationContext extends AbstractRefreshableConfigApplicationContext { @Override protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException { // 創建 XmlBeanDefinitionReader, 即創建 Bean 讀取器 // 並通過回調設置到容器中, 容器使用該讀取器讀取 Bean 配置資源 XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory); // 為 Bean 讀取器設置 Spring 資源加載器 // AbstractXmlApplicationContext 的祖先父類 AbstractApplicationContext 繼承 DefaultResourceLoader // 因此容器本身也是一個資源加載器 beanDefinitionReader.setEnvironment(this.getEnvironment()); beanDefinitionReader.setResourceLoader(this); // 為Bean 讀取器設置 SAX xml 解析器 beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this)); // 當Bean 讀取器讀取 Bean 定義 xml 資源文件時, 啟用 xml 的校驗機制 initBeanDefinitionReader(beanDefinitionReader); // Bean 讀取器真正實現加載的方法 loadBeanDefinitions(beanDefinitionReader); } } public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader { // 按照 Spring 的Bean 語義要求將 Bean 配置信息解析並轉換為容器內部數據結構 public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { /* 得到 BeanDefinitionDocumentReader 來對 XML 格式的 BeanDefinition 進行解析*/ BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); // 獲得容器中註冊的 Bean 總數 (包含內置 bean) int countBefore = getRegistry().getBeanDefinitionCount(); // 解析過程的入口, 這裡使用了委派模式, BeanDefinitionDocumentReader 只是一個接口 /* 具體的解析過程由實現類 DefaultBeanDefinitionDocumentReader 完成 */ documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); // 統計解析的 Bean 數量 return getRegistry().getBeanDefinitionCount() - countBefore; } } } // ----------------------------------------------- 分割線 --------------------------------------------------- // A 生產線 public class AnnotationConfigApplicationContext extends GenericApplicationContext implements AnnotationConfigRegistry { public void scan(String... basePackages) { Assert.notEmpty(basePackages, "At least one base package must be specified"); /* */ this.scanner.scan(basePackages); } } public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateComponentProvider { public int scan(String... basePackages) { // 獲得容器中註冊的 Bean 總數 (包含內置 bean) int beanCountAtScanStart = this.registry.getBeanDefinitionCount(); doScan(basePackages); // Register annotation config processors, if necessary. if (this.includeAnnotationConfig) { AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry); } // 統計解析的 Bean 數量 return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart); } protected Set doScan(String... basePackages) { Assert.notEmpty(basePackages, "At least one base package must be specified"); Set beanDefinitions = new LinkedHashSet<>(); for (String basePackage : basePackages) { /* */ Set candidates = findCandidateComponents(basePackage); for (BeanDefinition candidate : candidates) { ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate); candidate.setScope(scopeMetadata.getScopeName()); String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry); if (candidate instanceof AbstractBeanDefinition) { postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName); } if (candidate instanceof AnnotatedBeanDefinition) { AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate); } if (checkCandidate(beanName, candidate)) { BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName); definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry); beanDefinitions.add(definitionHolder); /* */ registerBeanDefinition(definitionHolder, this.registry); } } } return beanDefinitions; } } /<code>
上述代碼 表示 兩條生產線定位 載入 是不同的, 但從 UML 類圖看 它倆 都繼承了 AbstractApplicationContext 所以 註冊 DI 是相同的
<code>public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable { // 存儲註冊信息 BeanDefinition private final Map beanDefinitionMap = new ConcurrentHashMap<>(256); @Override public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionStoreException { Assert.hasText(beanName, "Bean name must not be empty"); Assert.notNull(beanDefinition, "BeanDefinition must not be null"); // 校驗解析的 beanDefinition if (beanDefinition instanceof AbstractBeanDefinition) { try { ((AbstractBeanDefinition) beanDefinition).validate(); } catch (BeanDefinitionValidationException ex) { throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName, "Validation of bean definition failed", ex); } } BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName); if (existingDefinition != null) { if (!isAllowBeanDefinitionOverriding()) { throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition); } else if (existingDefinition.getRole() < beanDefinition.getRole()) { // e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE if (logger.isInfoEnabled()) { logger.info("Overriding user-defined bean definition for bean '" + beanName + "' with a framework-generated bean definition: replacing [" + existingDefinition + "] with [" + beanDefinition + "]"); } } else if (!beanDefinition.equals(existingDefinition)) { if (logger.isDebugEnabled()) { logger.debug("Overriding bean definition for bean '" + beanName + "' with a different definition: replacing [" + existingDefinition + "] with [" + beanDefinition + "]"); } } else { if (logger.isTraceEnabled()) { logger.trace("Overriding bean definition for bean '" + beanName + "' with an equivalent definition: replacing [" + existingDefinition + "] with [" + beanDefinition + "]"); } } this.beanDefinitionMap.put(beanName, beanDefinition); } else { if (hasBeanCreationStarted()) { // 註冊的過程中需要線程同步, 以保證數據的一致性 synchronized (this.beanDefinitionMap) { this.beanDefinitionMap.put(beanName, beanDefinition); List updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1); updatedDefinitions.addAll(this.beanDefinitionNames); updatedDefinitions.add(beanName); this.beanDefinitionNames = updatedDefinitions; if (this.manualSingletonNames.contains(beanName)) { Set updatedSingletons = new LinkedHashSet<>(this.manualSingletonNames); updatedSingletons.remove(beanName); this.manualSingletonNames = updatedSingletons; } } } else { this.beanDefinitionMap.put(beanName, beanDefinition); this.beanDefinitionNames.add(beanName); this.manualSingletonNames.remove(beanName); } this.frozenBeanDefinitionNames = null; } // 檢查是否已經註冊過同名的 beanDefinition if (existingDefinition != null || containsSingleton(beanName)) { // 重置所有已經註冊過的 beanDefinition 緩存 resetBeanDefinition(beanName); } } } /<code>
總攬全局, 可以看到我們講的 IOC 與 DI 只是眾多 Spring 生命週期中的一部分.
<code>public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext { @Override public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // 1. 調用容器準備的刷新方法, 獲取容器的當前時間, 同時給容器設置同步標識 prepareRefresh(); // 2. 告訴子類啟動 refreshBeanFactory()方法, Bean定義資源文件的載入從子類的 refreshBeanFactory() 方法啟動 // 繼承了 AbstractRefreshableApplicationContext 的容器子類可以調用, 從 UML 圖上看 X 生產線可以,A 生產線不行 ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // 3. 為 BeanFactory 配置容器特性, 例如類加載器, 事件處理器等 prepareBeanFactory(beanFactory); try { // 4. 為容器的某些子類指定的特殊的 Post 事件處理器 postProcessBeanFactory(beanFactory); // 5. 調用所有註冊的 BeanFactoryPostProcessor 的 Bean invokeBeanFactoryPostProcessors(beanFactory); // 6. 為 BeanFactory 註冊 Post 事件處理器 // BeanPostProcessor 是Bean 後置處理器, 用於監聽容器觸發的事件 registerBeanPostProcessors(beanFactory); // 7. 初始化信息源, 和國際化相關 initMessageSource(); // 8. 初始化容器事件傳播器 initApplicationEventMulticaster(); // 9. 調用子類的某些特殊的Bean的初始化方法 onRefresh(); // 10. 為事件傳播器註冊事件監聽器 registerListeners(); // 11. 初始化所有剩餘的單例模式Bean (non-lazy-init) finishBeanFactoryInitialization(beanFactory); // 12. 初始化容器的生命週期事件處理器,併發布容器的生命週期事件 finishRefresh(); } catch (BeansException ex) { if (logger.isWarnEnabled()) { logger.warn("Exception encountered during context initialization - " + "cancelling refresh attempt: " + ex); } // 13. 銷燬已經創建的單例Bean,以避免掛起資源。 destroyBeans(); // 14. 取消刷新操作, 重置容器的同步標識 cancelRefresh(ex); // Propagate exception to caller. throw ex; } finally { // 15. 重設公共緩存, 可能再也不需要單例bean的元數據了…… resetCommonCaches(); } } } }/<code>
DI 依賴注入
循環依賴就發生在 DI 依賴注入這一步. 接下來我們詳細探討一下 它的原理. 看千遍不如手動搞一遍, 不然只是別人的知識,
依賴注入觸發規則
- 用戶第一次調用 getBean 方法時, IOC 容器觸發依賴注入
- Bean 設置為 懶加載, 在需要預實例化 Bean 時觸發依賴注入
<code>public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext { // 11. 初始化所有剩餘的單例模式Bean (non-lazy-init) protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) { ... beanFactory.preInstantiateSingletons(); } } public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable { @Override public void preInstantiateSingletons() throws BeansException { List beanNames = new ArrayList<>(this.beanDefinitionNames); // 觸發所有非惰性單例bean的實例化… for (String beanName : beanNames) { RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName); if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) { if (isFactoryBean(beanName)) { ... } else { // 第一次調用 getBean 方法時, IOC 容器觸發當前 Bean的依賴注入與實例化 getBean(beanName); } } } ... } } public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory { @Override public Object getBean(String name) throws BeansException { return doGetBean(name, null, null, false); } protected T doGetBean(final String name, @Nullable final Class requiredType, @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException { // 根據指定的名稱獲取被管理的 Bean 名稱, 剝離指定名稱中對容器的相關依賴 // 如果指定的是別名, 將別名轉換為 規範的 Bean 名稱 final String beanName = transformedBeanName(name); Object bean; // 先從緩存中讀取是否已經有被創建過的單例模式的 Bean // 對於單例模式的Bean 整個 IOC 容器中只創建一次, 不需要重複創建 Object sharedInstance = getSingleton(beanName); // IOC 容器創建單例模式的 Bean 示例對象 if (sharedInstance != null && args == null) { if (logger.isTraceEnabled()) { // 如果在容器中已有指定名稱的單例模式 Bean 被創建, 直接返回已經創建的 Bean if (isSingletonCurrentlyInCreation(beanName)) { logger.trace("Returning eagerly cached instance of singleton bean '" + beanName + "' that is not fully initialized yet - a consequence of a circular reference"); } else { logger.trace("Returning cached instance of singleton bean '" + beanName + "'"); } } // 注意: FactoryBean 是創建對象的工廠 Bean, BeanFactory 是管理 Bean 的工廠 // 獲取給定 Bean 的實例對象, 主要完成 FactoryBean 的相關處理 bean = getObjectForBeanInstance(sharedInstance, name, beanName, null); } else { // 緩存中沒有正在創建的 單例模式的 Bean // 緩存中已有原型模式的 Bean // 但是由於循環依賴導致實例化對象失敗 if (isPrototypeCurrentlyInCreation(beanName)) { throw new BeanCurrentlyInCreationException(beanName); } // 對 IOC 容器中是否存在指定名稱的 BeanDefinition 進行檢查 // 首先檢查是否能對當前的 BeanFactory 中獲取所需要的 Bean, // 如果不能則委託當前容器的父容器去查找, 如果還是找不到則沿著容器的繼承體系向父容器查找 BeanFactory parentBeanFactory = getParentBeanFactory(); if (parentBeanFactory != null && !containsBeanDefinition(beanName)) { // 解析指定 Bean 名稱的原始名稱 String nameToLookup = originalBeanName(name); if (parentBeanFactory instanceof AbstractBeanFactory) { return ((AbstractBeanFactory) parentBeanFactory).doGetBean( nameToLookup, requiredType, args, typeCheckOnly); } else if (args != null) { // 委託父容器根據指定名稱和顯式的參數查找 return (T) parentBeanFactory.getBean(nameToLookup, args); } else if (requiredType != null) { // 委託父容器根據指定 名稱和類型查找 return parentBeanFactory.getBean(nameToLookup, requiredType); } else { // 委託父容器根據指定 名稱查找 return (T) parentBeanFactory.getBean(nameToLookup); } } // 創建的 Bean 是否需要進行類型驗證, 一般不需要 if (!typeCheckOnly) { // 向容器標記指定的 Bean 已經被創建 markBeanAsCreated(beanName); } try { // 根據指定 Bean 名稱獲取其父級 Bean 定義 // 主要解決 Bean 繼承子類和父類公共屬性問題 final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName); checkMergedBeanDefinition(mbd, beanName, args); // 獲取當前 Bean 所有依賴 Bean 的名稱 String[] dependsOn = mbd.getDependsOn(); /** 如果當前 Bean 有 @DependsOn 依賴的 Bean */ if (dependsOn != null) { for (String dep : dependsOn) { if (isDependent(beanName, dep)) { throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Circular depends-on relationship between '" + beanName + "' and '" + dep + "'"); } // 把被依賴 Bean 註冊給當前依賴的 Bean registerDependentBean(dep, beanName); try { // 遞歸調用 getBean()方法, 獲取給當前依賴 Bean getBean(dep); } catch (NoSuchBeanDefinitionException ex) { throw new BeanCreationException(mbd.getResourceDescription(), beanName, "'" + beanName + "' depends on missing bean '" + dep + "'", ex); } } } /** 創建單例模式的 Bean 的實例對象*/ if (mbd.isSingleton()) { // 這裡使用了一個匿名的內部類創建 Bean 實例對象, 並且註冊給所依賴的對象 sharedInstance = getSingleton(beanName, () -> { try { // 創建一個指定的 Bean 的實例對象, 如果有父級繼承, 則會合並子類和父類的定義 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. // 顯式地從容器中單例模式的 Bean 緩存中清除實例對象 destroySingleton(beanName); throw ex; } }); // 獲取給定的 Bean 實例對象 bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); } /** 創建原型模式 Bean 的實例對象*/ else if (mbd.isPrototype()) { // 原型模式 (Prototype) 每次都會創建一個新的對象 Object prototypeInstance = null; try { // 回調 beforePrototypeCreation() 方法, 默認的功能是註冊當前創建的原型對象 beforePrototypeCreation(beanName); // 創建指定 Bean 的對象實例 prototypeInstance = createBean(beanName, mbd, args); } finally { // 回調 afterPrototypeCreation() 方法, 默認的功能是告訴 IOC 容器 不再創建指定 Bean 的原型對象 afterPrototypeCreation(beanName); } bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd); } /** 創建 Web Bean 的實例對象, 比如 request , session, application 等生命週期*/ else { // 要創建的 Bean 既不是單例模式的, 也不是原型模式的, 則根據 Bean 定義資源中 // 配置的生命週期範圍, 選擇實例化 Bean 的合適方法, 這種方式在多用於 Web 應用程序中 String scopeName = mbd.getScope(); final Scope scope = this.scopes.get(scopeName); // 如果Bean 定義資源中沒有配置生命週期範圍, 則Bean定義不合法 if (scope == null) { throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'"); } try { // 這裡又使用了一個匿名內部類, 獲取一個指定生命週期範圍的實例 Object scopedInstance = scope.get(beanName, () -> { beforePrototypeCreation(beanName); try { return createBean(beanName, mbd, args); } finally { afterPrototypeCreation(beanName); } }); // 獲取指定 Bean 的實例對象 bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd); } catch (IllegalStateException ex) { .... } } } catch (BeansException ex) { cleanupAfterBeanCreationFailure(beanName); throw ex; } } ... return (T) bean; } } protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args) throws BeanCreationException { // 封裝被創建的 Bean 對象 BeanWrapper instanceWrapper = null; if (mbd.isSingleton()) { instanceWrapper = this.factoryBeanInstanceCache.remove(beanName); } if (instanceWrapper == null) { instanceWrapper = createBeanInstance(beanName, mbd, args); } final Object bean = instanceWrapper.getWrappedInstance(); // 獲取實例化對象的類型 Class> beanType = instanceWrapper.getWrappedClass(); if (beanType != NullBean.class) { mbd.resolvedTargetType = beanType; } // 調用 PostProcessor 後置處理器 synchronized (mbd.postProcessingLock) { if (!mbd.postProcessed) { try { applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName); } catch (Throwable ex) { ... } mbd.postProcessed = true; } } // 向容器中 緩存單例模式的 Bean 對象, 以防止循環引用 boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName)); if (earlySingletonExposure) { // 這裡是一個匿名內部類, 為了循環引用, 儘早持有對象的引用 // 第二個參數是回調接口,實現的功能是將切面動態織入 bean addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); } // Bean 對象的初始化, 依賴注入在此觸發 Object exposedObject = bean; try { // 將 Bean 實例對象封裝, 並且將 Bean 定義中配置的屬性值賦給實例對象 populateBean(beanName, mbd, instanceWrapper); // 調用初始化方法,例如 init-method exposedObject = initializeBean(beanName, exposedObject, mbd); } catch (Throwable ex) { if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) { throw (BeanCreationException) ex; } else { throw new BeanCreationException( mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex); } } if (earlySingletonExposure) { // 獲取指定名稱的已註冊的單例模式 Bean 對象 Object earlySingletonReference = getSingleton(beanName, false); if (earlySingletonReference != null) { // 根據名稱獲取 的已註冊的 Bean 和正在實例化的 Bean 是同一個 if (exposedObject == bean) { // 當前實例化的 Bean 初始化完成 exposedObject = earlySingletonReference; } // 當前 Bean 依賴其他 Bean, 並且當發生循環引用時不允許創建新的實例對象 else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) { String[] dependentBeans = getDependentBeans(beanName); Set actualDependentBeans = new LinkedHashSet <>(dependentBeans.length); // 獲取當前 Bean 所依賴的其他 Bean for (String dependentBean : dependentBeans) { // 對依賴 Bean 進行類型檢查 if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) { actualDependentBeans.add(dependentBean); } } if (!actualDependentBeans.isEmpty()) { ... } } } } // 註冊完成依賴注入的 Bean try { registerDisposableBeanIfNecessary(beanName, bean, mbd); } catch (BeanDefinitionValidationException ex) { throw new BeanCreationException( mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex); } return exposedObject; }/<code>
後話
Mason 能看懂源碼,主要有兩方面在起作用,
一方面是因為水滴石穿的不懈努力,客觀上起了作用,
一方面是因為 Mason 通過讀毛選掌握了克服困難的方法論, 主觀上起了作用,
推薦大家參加 毛三公的B站活動 #我在讀毛選#