Spring Ioc源碼分析系列--Bean實例化過程(一)

来源:https://www.cnblogs.com/codegitz/archive/2022/05/26/16314523.html
-Advertisement-
Play Games

Spring Ioc源碼分析系列--Bean實例化過程(一) 前言 上一篇文章Spring Ioc源碼分析系列--Ioc容器註冊BeanPostProcessor後置處理器以及事件消息處理已經完成了對IoC容器啟動方法也就是refresh()方法的簡單分析。但是之前的分析在對容器實例化Bean的過程 ...


Spring Ioc源碼分析系列--Bean實例化過程(一)

前言

上一篇文章Spring Ioc源碼分析系列--Ioc容器註冊BeanPostProcessor後置處理器以及事件消息處理已經完成了對IoC容器啟動方法也就是refresh()方法的簡單分析。但是之前的分析在對容器實例化Bean的過程的略過了,留到了這後續的文章分析,所以這篇文章會對Bean的實例化過程做一個介紹。

首先來理一下本文的思路:關鍵詞是實例化。由於Spring是利用反射實現的實例化,腦子裡先簡單想一下Java里利用發射實例化一個對象需要哪些步驟和操作。毫無疑問,我們首先要知道對象的class,接著需要確定使用什麼構造函數以及確定構造函數的參數等。利用這些已經基本可以實現一個對象的實例化,當然實際上需要的東西可能更多更複雜,這裡只是舉個例子。那麼需要的這些信息可以去哪裡提取呢?對Spring有瞭解的可能都馬上能想到BeanDefinition,這是一份原料表,裡面有我們構造一個實例化對象所需的所有參數。如果不太理解這個定義,可以參考一下上篇文章的例子。

如果不清楚BeanDefinition是從哪裡來的以及不清楚如何定義的,可以參考之前的文章Spring Ioc源碼分析系列--Ioc源碼入口分析的關鍵實現系列方法 loadBeanDefinitions ()。這篇文章講解註冊的時候只是說了註冊到容器里,並沒有說明具體是註冊到了哪裡,這裡點明一下,所謂講BeanDefinition註冊到容器里,就是將BeanDefinition放入到容器的一個Map里,具體是註冊到了DefaultListableBeanFactorybeanDefinitionMap屬性里,beanName會保存到beanDefinitionNames屬性里,這是個list集合,裡面的beanName會保持註冊時候的順序。

實例化的開始就是從遍歷所有的beanName開始,話不多說,開始分析吧。

源碼分析

bean實例化入口

還記得實例化入口的方法名嗎?回憶一下,算了,反正也不會有人記得。是beanFactory.preInstantiateSingletons(),具體實現是在DefaultListableBeanFactory類里。

跟進代碼查看,可以看到,這段代碼分為兩部分,第一個for迴圈用於先實例化對象,第二個for迴圈完成一些實例化之後的回調操作。我們先來看第一個for迴圈,首先是遍歷所有的beanNames獲取BeanDefinition,然後根據工廠bean非工廠bean進行相應處理,最後調用getBean(beanName)實例化對象。註意這裡實例化的是非抽象的、單例的並且是非懶載入的bean,這個前提非常重要。

	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.
		// 所有bd的名稱
		List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);

		// Trigger initialization of all non-lazy singleton beans...
		// 遍歷所有bd,一個個進行創建
		for (String beanName : beanNames) {
			// 獲取到指定名稱對應的bd
			RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
			// 對不是延遲載入的單例的Bean進行創建
			if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
				// 判斷是否是一個FactoryBean
				if (isFactoryBean(beanName)) {
					// 如果是一個factoryBean的話,先創建這個factoryBean,創建factoryBean時,需要在beanName前面拼接一個&符號
					Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
					if (bean instanceof FactoryBean) {
						final FactoryBean<?> factory = (FactoryBean<?>) bean;
						boolean isEagerInit;
						if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
							isEagerInit = AccessController.doPrivileged((PrivilegedAction<Boolean>)
											((SmartFactoryBean<?>) factory)::isEagerInit,
									getAccessControlContext());
						}
						else {
							// 判斷是否是一個SmartFactoryBean,並且不是懶載入的,就意味著,在創建了這個factoryBean之後要立馬調用它的getObject方法創建另外一個Bean
							isEagerInit = (factory instanceof SmartFactoryBean &&
									((SmartFactoryBean<?>) factory).isEagerInit());
						}
						if (isEagerInit) {
							getBean(beanName);
						}
					}
				}
				else {
					// 不是factoryBean的話,我們直接創建就行了
					getBean(beanName);
				}
			}
		}

		// Trigger post-initialization callback for all applicable beans...
		// 在創建了所有的Bean之後,遍歷為所有適用的 bean 觸發初始化後回調,也就是這裡會對延遲初始化的bean進行載入...
		for (String beanName : beanNames) {
			// 這一步其實是從緩存中獲取對應的創建的Bean,這裡獲取到的必定是單例的
			Object singletonInstance = getSingleton(beanName);
			// 判斷是否是一個SmartInitializingSingleton,
			// 最典型的就是我們之前分析過的EventListenerMethodProcessor,
			// 在這一步完成了對已經創建好的Bean的解析,會判斷其方法上是否有 @EventListener註解,
			// 會將這個註解標註的方法通過EventListenerFactory轉換成一個事件監聽器並添加到監聽器的集合中
			if (singletonInstance instanceof SmartInitializingSingleton) {
				final SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance;
				if (System.getSecurityManager() != null) {
					AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
						smartSingleton.afterSingletonsInstantiated();
						return null;
					}, getAccessControlContext());
				}
				else {
					smartSingleton.afterSingletonsInstantiated();
				}
			}
		}
	}

獲取BeanDefinition

首先跟進getMergedLocalBeanDefinition(beanName)方法,這裡首先會嘗試從mergedBeanDefinitions里去獲取,這個mergedBeanDefinitions存放著已經合併過的BeanDefinition,獲取不到再真正調用getMergedBeanDefinition(beanName, getBeanDefinition(beanName))去獲取。

	/**
	 * Return a merged RootBeanDefinition, traversing the parent bean definition
	 * if the specified bean corresponds to a child bean definition.
	 *
	 * 返回一個合併的 RootBeanDefinition,如果指定的 bean 對應於子 bean 定義,則遍歷父 bean 定義。
	 *
	 * @param beanName the name of the bean to retrieve the merged definition for
	 * @return a (potentially merged) RootBeanDefinition for the given bean
	 * @throws NoSuchBeanDefinitionException if there is no bean with the given name
	 * @throws BeanDefinitionStoreException in case of an invalid bean definition
	 */
	protected RootBeanDefinition getMergedLocalBeanDefinition(String beanName) throws BeansException {
		// Quick check on the concurrent map first, with minimal locking.
		// 首先檢查 mergedBeanDefinitions ,最小程度影響併發性能
		RootBeanDefinition mbd = this.mergedBeanDefinitions.get(beanName);
		if (mbd != null && !mbd.stale) {
			return mbd;
		}
		return getMergedBeanDefinition(beanName, getBeanDefinition(beanName));
	}

先看getBeanDefinition(beanName),這個方法就是簡單的去beanDefinitionMap里獲取BeanDefinition,如果獲取不到,就拋出異常。beanDefinitionMap就是上面說到的BeanDefinition存放的地方。

	public BeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException {
		BeanDefinition bd = this.beanDefinitionMap.get(beanName);
		if (bd == null) {
			if (logger.isTraceEnabled()) {
				logger.trace("No bean named '" + beanName + "' found in " + this);
			}
			throw new NoSuchBeanDefinitionException(beanName);
		}
		return bd;
	}

接下來就進入到getMergedBeanDefinition()方法獲取BeanDefinition,為啥要從beanDefinitionMap獲取了還進行一個merged獲取呢?這是因為Bean有層次關係,子類需要合併父類的屬性方法等,所以要進行一次合併,合併完成後會放入到mergedBeanDefinitions里,功能和屬性名區分度還是十分貼切的

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • # 基礎語法 https://blog.csdn.net/m0_37989980/article/details/103413942 CRUD 提供給資料庫管理員的基本操作,CRUD(Create, Read, Update and Delete)。 1. 語法: select [distinct ...
  • 短視頻時代來臨,一部手機就可以玩轉多種花樣,所以越來越多的自由創作者加入這個行業,平時生活中用手機拍短視頻、街頭唱歌的非專業從業者隨處可見。離開了錄音棚,沒有專業、統一的錄音設備,無論在家裡還是在路邊、商場等地方,錄製的視頻帶噪音在所難免。所以在後期製作中,如何快速準確地處理雜訊至關重要。HMS C ...
  • 前言 【項目資源】longPressDemo 項目功能介紹 長按顯示菜單 【效果圖】 出發條目後,顯示提示信息 【效果圖】 項目技術支持 【開發環境】 Android Studio window11 【開發語言】 後端: Java 前端 xml 項目難點 如何設置出長按效果? 通過按鈕控制項綁定set ...
  • 題目:做一個電子時鐘,顯示當前的年月日,時分秒,要求自動變化。 案例分析: 1.使用一個div盒子來展示時鐘的內容; 2.將盒子在JavaScrip裡面獲取div盒子; 3.我們需要一個定時器setInterval每隔一秒使時鐘變化一次; 4.利用時間函數Date()獲取系統時間,並分別獲取年月日, ...
  • HashMap源碼 目錄 1.1 包含的屬性 1.2 構造器 1.3 hash方法源碼 1.4 put源碼 1.5 resize源碼 1.6 table 變數為什麼用transient 修飾 1.1 包含的屬性 public class HashMap<K,V> extends AbstractMa ...
  • 最近要給小伙伴們寫幾篇文章,關於《linux下誤刪除文件之後該如何恢復》。對於沒有進程占用的文件想要進行數據恢復,不同的文件系統格式需要使用不同的工具,比如:ext4、xfs等。我找遍了我所有的虛擬機伺服器,都沒找到ext4文件格式的。因為ext4畢竟還是非常常用的文件系統格式,我寫東西就希望能夠系 ...
  • 微信支付是企業級項目中經常使用到的功能,作為後端開發人員,完整地掌握該技術是十分有必要的。 ...
  • 原文地址:TornadoFx設置保存功能(config和preference使用) 相信大部分的桌面軟體都是存在一個設置的界面,允許用戶進行設置的修改,此修改之後需要保存的本地,若是讓開發者自己實現,還是有些繁瑣 這裡介紹下TornadoFx中提供的一個config對象,可以快速實現設置頁面相關數據 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...