【Spring專場】「IOC容器」不看原始碼就帶你認識核心流程以及運作原理

浩宇天尚發表於2022-01-08

這是史上最全面的Spring的核心流程以及運作原理的分析指南

  • ?【Spring核心專題】「IOC容器篇」不看繁瑣的原始碼就帶你瀏覽Spring的核心流程以及運作原理

  • ?【Spring核心專題】「AOP容器篇」不看繁瑣的原始碼就帶你瀏覽Spring的核心流程以及運作原理

  • ?【Spring核心專題】「MVC容器篇」不看繁瑣的原始碼就帶你瀏覽Spring的核心流程以及運作原理

學好Spring技術的背景

針對於每一個Java的愛好者而言,無論是從事面向於微服務架構技術的領域(SpringCloud、SpringCloud-Alibaba等),還是面向於傳統網際網路行業(SpringBoot)以及軟體系統(Spring\SpringBatch)領域,掌握好Spring框架技術原理和原始碼對排查問題以及未來的面試技術有著非常重要的幫助和影響,而接下來,筆者會針對於Spring的技術框架的核心原始碼流程點進行相關的分析和認識,相信閱讀完本篇文章,一定會對Spring的原始碼和執行原理有著很大的幫助和提升。

分析框架核心流程

獲取Spring框架的IOC容器

IOC容器執行流程主要核心流程點:

  • 獲取單例Bean物件
  • 建立單例Bean物件
  • 建立原始Bean物件
  • 解決迴圈依賴
  • 填充屬性資訊
  • 初始化Bean物件
getBean方法的執行流程
  1. 第一步將beanName或者BeanType型別進行獲取相關的容器資料物件,例如:處理以&符號開頭的name名稱資料,以及根據相關的alias別名。
  2. 第二步將存在根據 名稱或者別名進行獲取相關的快取池找那個進行獲取相關的物件例項
    • 如果存在:Spring框架會呼叫getObjectForBeanInstance方法,返回對應的Bean例項物件,其中Bean例項的型別有兩種模式:單例模式和原型模式
      • 單例模式:快取中沒有,建立一個,然後放入快取中,其中會對該單例物件bean進行先關的攔截和後置工作。
      • 原型模式:每次都會建立新的物件進行返回相關的物件。
    • 如果當前的容器中,無法獲取到相關的對應的BeanName的物件例項,則會進行想父容器進行尋找對應的物件Bean例項,如果父容器中存在,直接返回父容器中的資料物件例項,但是如果父容器還不存在,則會進行建立Bean物件例項了,但是在建立之前,會進行解析兩種特殊的Bean操作關係。
兩種特殊的Bean例項的關聯關係
  • parent bean的繼承關係,例如,a bean物件可以在xml檔案中繼承相關 a-parent bean的屬性以及相關的覆蓋操作

  • 處理相關的depend-ons依賴關係操作,這樣子可以根據依賴關係,建立一個載入和建立Bean之間的前後關係和依賴關係,例如A depend-ons B的bean物件,那麼在建立A之前一定會先載入和建立B,依此類託。

之後進行相關的建立bean的操作控制!

獲取Spring框架的變數容器
  • singletonObjects:單例一級快取池-用於存放完全例項化+初始化好的物件Bean,如果從該快取池中取出的Bean可以直接的使用。

  • earlySingletonObject:單例二級快取池-用於存放正在初始化的物件bean,主要用於解決迴圈依賴的臨時存放的物件池。

  • singletonFactories:用於存放bean物件的工廠物件機制,主要用於建立bean物件的ObjectFactory。

createBean方法的執行流程
  • createBean的方法入口,getSIngleton方法:
    1. 先從singletonObjects集合中獲取相關的Bean例項,若不為空,則直接返回。
    2. 如果獲取不到相關的物件例項在一級單例快取池中,則會進行createBeanInstance例項階段(此部分,接下來會詳細介紹),會將對應的BeanName新增到singleCurrentlyInCreation集合中,這個集合主要用於存放相關的將要建立的物件bean,這個是第一步。
    3. 當通過getObject方法呼叫createBean方法的是建立例項物件的完成之後,會將物件例項從singleCurrentlyInCreation集合中進行轉移到singleObjects物件集合快取池中,對映關係為:beanName->singleObject物件。
createBean的方法要點

解析Bean的型別和屬性型別特點分析,主要分為以下幾點內容:

  1. 解析相關的Bean物件的型別。
  2. 校驗和分析處理相關的override註解修飾的方法,主要用於先去校驗和分析是否存在過載方法或者覆蓋方法,方便cglib動態代理的時候不需要進行校驗,而是直接處理呼叫即可。
    • 其中有一個屬性:lookup-method,如果我們希望在單例物件裡面加入一個原型模式(prototype)的物件屬性,那麼可以考慮使用<lookup-method name="getPrototypeBean", bean = "prototypeBean" /> ApplicationContextAware。
  3. bean例項化前的後置處理控制hook鉤子函式以及相關回撥機制控制。
createBean的最核心方法doCreateBean
  • 呼叫doCreateBean建立bean例項,此方法算是最底層的建立createBean的代表方法了,首先他會遵循從快取中區獲取相關的BeanWrapper實現類物件,並且清除一些臨時資料資訊。
  • 如果快取中沒有相關的快取,則會進行手動建立bean例項物件,將例項物件包裹在BeanWrapper例項類物件並且返回該BeanWrapper物件。
  • 並且採用MergeBeanDefinitionBeanPostProcessor的後置處理器,對相關的物件的abstract和parent的繼承關係的bean進行合併處理。
  • 根據系統的配置是否支援迴圈依賴的選項,進行選擇和決定是否採用提前暴露bean的早期引用(early reference),主要用於處理的迴圈依賴。
  • 之後對相關的提前暴露的引用和屬性欄位進行使用popluateBean方法進行引用的屬性進行填充,其中也包含了相關的迴圈引用的概念在裡面。
  • 呼叫相關的initializeBean方法完成餘下的初始化工作任務,包含了:initializeBean介面實現、@PostConstruct註解處理控制、以及init-method方法的屬性處理。
  • 註冊銷燬相關的distroy-method的屬性以及相關的preDestory的方法控制。
doCreateBean建立最原始的Bean物件

主要通過createBeanInstance方法例項機制,其核心流程為:

  • 檢測類的訪問許可權,若禁止訪問,則會丟擲異常機制。
  • 如果該物件bean的factory-method屬性包含了factory工廠方法機制不為空,則通過該定義的宣告的相關的factory方法進行建立bean,並且返回結果。
通過相關的構造器的方式進行構建物件

在此我們會採用construct的方式進行反射進行構建例項物件,並且返回物件的物件結果,步驟如下:

  1. 建立相關的BeanWrapperImpl物件作為先關的Bean例項物件的包裝實現類。
  2. 之後需要進行構建相關的真實的原始模型物件,其中上面說了,如果該bean定義擁有相關的factory方法,則會直接通過factory方法建立,否則會採用構造器的方式進行構建哦!
  3. 會針對於該物件的所有定義以及隱含的構造器進行分析和處理,採用minOrArg方式計算出,進行分析出了一個按照引數數量進行排序的構造器列表。(其中會包含著訪問優先順序以及引數個數的條件進行排序)。
  4. 一般預設而言,會使用最少引數的構造器,當然如果存在預設構造器,一般會採用預設構造器區進行處理,但是如果存在非預設的構造器,則會採用引數注入的方式進行構造器進行構建。
  5. 核心: 我們前面已經將構造器列表進行排序完成後,會進行篩選獲取合適的構造器進行執行構建物件。如果我們獲取到了一個含有引數的構造器,那麼spring框架會怎麼做?
    • 先進行獲取相關構造器中的所有相關的形式引數的名稱以及型別。
    • 在進行解析引數,此解析方式會將對一些已經儲存在容器中的資料進行解析注入以及相關的型別引數轉換機制。
    • 從而計算構造器與數值型別的差異性,選擇最佳何時的構造器方法。
    • 當我們已經篩選出和是的構造方法(最終),如果在此使用建立bean物件例項的時候,可以直接使用,無需在進行篩選。
    • 之後我們採用初始化策略進行構建該例項bean物件。
    • 最後將該物件注入到我們的BeanWrapperImpl物件模型中,並返回物件。
如果通過構造器或者工廠方法都無法構建

那麼會採用組合方式進行構建該物件

  1. 通過工廠方法進行構建
  2. 通過自定義構造器進行構建
  3. 通過預設構造器進行構建

構建的方式需要配合動態代理機制

為了方便我們進行在對Springbean容器的物件進行AOP攔截操作處理機制。

解決迴圈依賴

話不多說,就是提前暴露,可以通過factory避過去以及@lazy不會引起錯誤等。

IOC容器篇

主要的方法為populateBean方法

popluteBean的方法的執行流程

首先會獲取相關的注入該類物件bean的屬性列表,我們再切定義為pvs。

  1. 當構造器構建完物件之後會進行相關的自定義屬性進行填充,但是在進行相關的屬性填充進行之前,會先去嘗試採用系統預設後置處理器進行填充。

主要通過引數名或者引數型別進行解析並且填充相關的依賴屬性,主要可以通過的手段就是@Autowired或者@Resource、@Inject等。

  1. 之後還會在採用後置處理器對屬性進行動態pvs的內容進行填充處理。

  2. 會將屬性應用到bean中的applyProperyValues方法:

    • 在檢測屬性值是否已經完成轉換,如果該屬性值已經完成轉換,則直接使用,無需再次轉換。
    • 遍歷屬性列表,解析器屬性的原始值,在通過PropertisSourcePlaceholdConfigurer進行相關的解析操作,並且完成解析值resolveValue。
  3. 最後將的到的解析數值resolveValue進行相關的型別屬性轉換操作。

  4. 將型別轉換後的值設定到PropertyValue物件中,將PropertyValue物件存入deepCopy集合中,並且將deepCopy的屬性值注入到bean物件中。

根據名稱和型別進行填充
根據名稱注入

就是單純的將bean名稱進行注入到相關的非簡單型別的注入機制。

根據型別注入
  • 主要處理@Value註解進行注入操作解析機制!
  • 解析陣列、list、map等型別的依賴注入機制
  • 根據型別查詢相關何時的型別資料資訊
  • 如果候選項的數量為0,則丟擲異常。如果=1,則直接從候選列表中進行獲取,如果>1,則在多個候選選項中的獲取最優的物件,否則丟擲異常。
  • 如果候選選選為class型別,則標識候選選選還沒有完成例項化,此時通過BeanFactory.getBean的方式進行例項化,否則會直接返回物件例項。

初始化Bean物件

主要是經歷了所有的例項化和處理之後,則會需要進行相關的初始化方法的呼叫,在底層框架表現為initializeBean方法進行初始化,執行順序的判斷邏輯執行流程為:

  1. 檢測bean是否實現了xAware型別的介面,如果實現了,則會向該bean中注入相關的x的例項屬性物件,主要通過呼叫invokeAwareMethods方法。
  2. 之後開始執行初始化的前置操作:例如BeanPostProcessor以及相關的afterPropertiesSetting方法。
  3. 執行相關的初始化操作invokeInitMethods方法。
  4. 執行後置的初始化操作,例如BeanPostProcessor的後置處理機制操作。

相關文章