Spring源碼:Bean的生命周期(二)

来源:https://www.cnblogs.com/guoxiaoyu/archive/2023/05/01/17367019.html
-Advertisement-
Play Games

FactoryBean 和 BeanFactory 是兩個不同的概念。前者是一個介面,我們可以在實現該介面時通過調用 getObject 方法來返回實例,同時 FactoryBean 本身也是一個實例。後者是 Spring 容器的工廠,通過其中的 bean 定義 Map 一個一個地實例化我們通過註解... ...


前言

讓我們繼續講解Spring的Bean實例化過程。在上一節中,我們已經講解了Spring是如何將Bean定義加入到IoC容器中,並使用合併的Bean定義來包裝原始的Bean定義。接下來,我們將繼續講解Spring的 getBean() 方法,特別是針對 FactoryBean 的解析。

getBean() 方法中,Spring還支持對 FactoryBean 進行特殊處理。FactoryBean 是一個能夠生成Bean實例的工廠Bean,其定義了 getObject() 方法,返回的是一個由工廠Bean管理的對象實例。在使用 getBean() 方法獲取 FactoryBean 類型的Bean時,Spring會首先獲取 FactoryBean 的實例,然後調用其 getObject() 方法來獲取由工廠Bean創建的實際Bean實例。

因此,在使用 getBean() 方法獲取Bean實例時,我們需要註意是否需要對 FactoryBean 進行特殊處理。如果需要獲取 FactoryBean 的實例而不是它所管理的對象實例,可以在Bean名稱前加上 & 符號來進行標識。例如:&myFactoryBean 表示獲取 myFactoryBean 的實例。但是博主看到第一篇源碼寫的篇幅確實有些長,可能對於大家伙的碎片化時間掌握的不是很充分,所以以後我會儘力控制篇幅長度,既保證邏輯的連續性也保證儘快可以看完,那麼接下來開始進入正題getbean方法之FactoryBean解析。

FactoryBean

所有符合過濾條件的Bean在Spring解析後都會被轉化為合併後的Bean定義。儘管Spring提供了 getBean() 方法用於獲取Bean實例,但實際上它底層仍然使用 createBean() 方法來創建Bean實例。在創建Bean實例之前,Spring先對當前Bean定義進行判斷,以確定其是否為 FactoryBean 類型:

public void preInstantiateSingletons() throws BeansException {  
   if (logger.isTraceEnabled()) {  
      logger.trace("Pre-instantiating singletons in " + this);  
   }  
  
   // Iterate over a copy to allow for init methods which in turn register new bean definitions.  
 // While this may not be part of the regular factory bootstrap, it does otherwise work fine.  List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);  
  
   // Trigger initialization of all non-lazy singleton beans...  
  for (String beanName : beanNames) {  
      // 獲取合併後的BeanDefinition  
  RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);  
  
      if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {  
         if (isFactoryBean(beanName)) {  
            // 獲取FactoryBean對象  
  Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);  
            if (bean instanceof FactoryBean) {  
               FactoryBean<?> factory = (FactoryBean<?>) bean;  
               boolean isEagerInit;  
               if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {  
                  isEagerInit = AccessController.doPrivileged(  
                        (PrivilegedAction<Boolean>) ((SmartFactoryBean<?>) factory)::isEagerInit,  
                        getAccessControlContext());  
               }  
               else {  
                  isEagerInit = (factory instanceof SmartFactoryBean &&  
                        ((SmartFactoryBean<?>) factory).isEagerInit());  
               }  
               if (isEagerInit) {  
                  // 創建真正的Bean對象(getObject()返回的對象)  
  getBean(beanName);  
               }  
            }  
         }  
         else {  
            // 創建Bean對象  
  getBean(beanName);  
         }  
      }  
   }  
  
   // 所有的非懶載入單例Bean都創建完了後  
  // Trigger post-initialization callback for all applicable beans...  
  for (String beanName : beanNames) {  
      Object singletonInstance = getSingleton(beanName);  
      if (singletonInstance instanceof SmartInitializingSingleton) {  
         StartupStep smartInitialize = this.getApplicationStartup().start("spring.beans.smart-initialize")  
               .tag("beanName", beanName);  
         SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance;  
         if (System.getSecurityManager() != null) {  
            AccessController.doPrivileged((PrivilegedAction<Object>) () -> {  
               smartSingleton.afterSingletonsInstantiated();  
               return null;  
            }, getAccessControlContext());  
         }  
         else {  
            smartSingleton.afterSingletonsInstantiated();  
         }  
         smartInitialize.end();  
      }  
   }  
}

他的源碼邏輯大致如下:

  1. Spring會根據 beanNamebean 定義 Map 中獲取當前合併的 Bean 定義。
  2. Spring會對當前 Bean 定義進行判斷,包括判斷當前 Bean 是否為抽象的、是否為單例、是否懶載入,以及是否為 FactoryBean。如果是 FactoryBean,則會走 FactoryBean 的創建邏輯,否則會走單例 Bean 的創建邏輯。
  3. 當所有單例非懶載入的 Bean 創建完成後,Spring會遍歷所有單例 Bean,判斷其是否為 SmartInitializingSingleton 類型。如果是,則會自動調用 afterSingletonsInstantiated 方法。

isFactoryBean

由於創建 Bean 的邏輯比較複雜,其中包含了許多細節,因此,在這裡我們特別提到了一個方法 isFactoryBean()。之所以要提到這個方法,是因為Spring支持使用 FactoryBean 來創建複雜對象。下麵是該方法的主要源碼:

public boolean isFactoryBean(String name) throws NoSuchBeanDefinitionException {  
   String beanName = transformedBeanName(name);  
   Object beanInstance = getSingleton(beanName, false);  
   if (beanInstance != null) {  
      return (beanInstance instanceof FactoryBean);  
   }  
   // No singleton instance found -> check bean definition.  
  if (!containsBeanDefinition(beanName) && getParentBeanFactory() instanceof ConfigurableBeanFactory) {  
      // No bean definition found in this factory -> delegate to parent.  
  return ((ConfigurableBeanFactory) getParentBeanFactory()).isFactoryBean(name);  
   }  
   return isFactoryBean(beanName, getMergedLocalBeanDefinition(beanName));  
}

大致邏輯如下:

  1. transformedBeanName 的作用是不管傳入的參數是 &××× 還是 ×××,都返回 ×××。這是因為Spring標記 FactoryBean 時使用 &××× 作為 FactoryBeanbeanName
  2. getSingleton 方法從單例池中獲取 Bean 實例,如果該實例是 FactoryBean,則直接返回該實例。
  3. 如果 BeanFactory 中的 Bean 定義 Map 中不包含該 beanNameBean 定義,並且當前 BeanFactory 的父 BeanFactory 實現了 ConfigurableBeanFactory 介面,那麼就需要查看當前父 BeanFactory 中是否有該實例,並且判斷該實例是否為 FactoryBean。舉個例子來說:
// 創建一個父Spring容器  
AnnotationConfigApplicationContext parent = new AnnotationConfigApplicationContext();  
parent.register(AppConfig.class);  
parent.refresh();  
// 創建一個Spring容器  
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();  
applicationContext.setParent(parent);  
applicationContext.register(AppConfig1.class);  
applicationContext.refresh();  
UserService bean = applicationContext.getBean(UserService.class);  
bean.test();
  1. 如果並沒有實例化出來的bean,那麼對bean定義進行判斷。
protected boolean isFactoryBean(String beanName, RootBeanDefinition mbd) {  
   Boolean result = mbd.isFactoryBean;  
   if (result == null) {  
      // 根據BeanDefinition推測Bean類型(獲取BeanDefinition的beanClass屬性)  
  Class<?> beanType = predictBeanType(beanName, mbd, FactoryBean.class);  
      // 判斷是不是實現了FactoryBean介面  
  result = (beanType != null && FactoryBean.class.isAssignableFrom(beanType));  
      mbd.isFactoryBean = result;  
   }  
   return result;  
}

註釋也基本寫好了,基本上就是根據BeanDefinition推測Bean類型(獲取BeanDefinition的beanClass屬性),再根據bean類型判斷是不是實現了FactoryBean介面,然後返回判斷結果。

SmartFactoryBean

getBean 方法中,我們可以獲取 FactoryBean 的實例並返回。接下來的步驟是判斷當前的 FactoryBean 是否實現了 SmartFactoryBean 介面。需要註意的是,SmartFactoryBeanFactoryBean 介面的一個子介面。雖然我們在實現 FactoryBean 介面時不必實現 SmartFactoryBean 介面,但是如果實現了 SmartFactoryBean 介面,那麼在創建 FactoryBean 時就會調用 getObject 方法返回實例。正常情況下,只有當容器啟動完成後才會調用 getObject 方法。如果我們想在初始化時就調用,可以這樣實現:

@Component  
public class UserFactory implements SmartFactoryBean {  
 
   @Override  
  public Object getObject() throws Exception {  
      return new User();  
   }  
  
   @Override  
  public Class<?> getObjectType() {  
      return User.class;  
   }  
  
   @Override  
  public boolean isEagerInit() {  
      return true;  
   }  
}

結語

FactoryBean 和 BeanFactory 是兩個不同的概念。前者是一個介面,我們可以在實現該介面時通過調用 getObject 方法來返回實例,同時 FactoryBean 本身也是一個實例。後者是 Spring 容器的工廠,通過其中的 bean 定義 Map 一個一個地實例化我們通過註解等方式註入進去的 bean 工廠。在判斷 FactoryBean 時,如果當前 BeanFactory 中沒有對應的 bean 定義,那麼就會去父容器中尋找相應的 bean 定義併進行判斷。如果我們的類實現了 SmartFactoryBean 介面,那麼它將會在 Spring 容器啟動時就會調用 getObject 方法創建實例。接下來,我們將分幾個小節來講解 getBean 方法是如何實例化 bean 的,因為篇幅過長會影響讀者的註意力和學習效果。

公眾號 ps:以上內容,純屬個人見解,有任何問題下方評論!關註博主公眾號,源碼專題、面試精選、AI最新擴展等你來看!原創編寫不易,轉載請說明出處!
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Runtime包 GOMAXPROCS() ​ 用來設置可以並行計算的CPU核數最大值,並返回之前的值,具體使用方法上一篇有些,這裡不再贅述 Gosched() ​ 用於讓出CPU時間片,讓出當前goroutine的執行許可權,調度器安排其他等待的任務運行,併在下次某個時候從該位置恢復執行 Goexi ...
  • 在前兩篇: .NET Core部署到linux(CentOS)最全解決方案,常規篇 .NET Core部署到linux(CentOS)最全解決方案,進階篇(Supervisor+Nginx) 我們對.netcore部署到linux有了一個充分的瞭解,已經可以滿足應用要求了,這篇文章我們繼續深入... ...
  • 這一篇簡單說明 CH32V208 的片記憶體儲結構和時鐘的特點, 以及通過 SDK 中的示例代碼分析 CH32V208 的時鐘設置 ...
  • CH32V208系列是沁恆32位RISC-V中比較新的一個系列, 基於青稞RISC-V4C內核, 最高144MHz主頻, 64KB SRAM,128KB Flash, 供電電壓2.5/3.3V. 這個型號的特點: 除了特有的硬體堆棧區、快速中斷入口, 片上集成了2Mbps低功耗藍牙BLE 5.3, ... ...
  • (初探MySQL) 前言 周所周知MySQL已成為全世界最受歡迎的資料庫之一。無論你用的何種編程語言在開發系統,資料庫基本上都是必不可少的。 無論是小型項目開發如我們開發一個個人博客系統,還是構建那些聲名顯赫的網站如某寶、某訊等,MySQL都有著穩定、可靠、快速等優點。可以勝任數據存儲的業務需求。 ...
  • 1. 消滅NULL 1.1. NULL惹人討厭的原因 1.1.1. 進行SQL編碼時,必須考慮違反人類直覺的三值邏輯 1.1.2. 指定IS NULL、IS NOT NULL的時候,不會用到索引,SQL語句執行起來性能低下 1.1.2.1. 1 + NULL = NULL 2- NULL = NUL ...
  • 前言 地址:https://www.cnblogs.com/FReQuenter5156/p/setblog.html/ 如題,使用的是 Simple Memory 主題。 Github 連接:https://github.com/BNDong/Cnblogs-Theme-SimpleMemory。 ...
  • 嘿嘿嘿、嘿嘿,俺又回來了! github代碼地址 https://github.com/Tom-shushu/work-study 介面文檔有道雲 https://note.youdao.com/s/GShGsYE8 介面文檔離線版本 https://files.cnblogs.com/files/ ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...