主頁 > 後端開發 > Spring原始碼:Bean生命周期(三)

Spring原始碼:Bean生命周期(三)

2023-05-05 08:18:35 後端開發

前言

在之前的文章中,我們已經對 bean 的準備作業進行了講解,包括 bean 定義和 FactoryBean 判斷等,在這個基礎上,我們可以更加深入地理解 getBean 方法的實作邏輯,并在后續的學習中更好地掌握createBean 方法的實作細節,

getBean用法

講解getBean方法之前,我們先來看看他有幾種常見的用法:

// 創建一個Spring容器  
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);  
UserService bean1 = applicationContext.getBean(UserService.class);  
UserService bean2 = (UserService)applicationContext.getBean("userService");  
UserService bean3 = applicationContext.getBean("userService",UserService.class);  
UserService bean4 = (UserService) applicationContext.getBean("userService",new OrderService());  
bean1.test();  
bean2.test();  
bean3.test();  
bean4.test();

關于獲取 bean 的方法,前兩種方法應該比較常見,這里就不再贅述,第三種方法實際上是在獲取 bean 的時候,會先判斷是否符合指定的型別,如果符合,則進行型別轉換并回傳對應的 bean 實體,第四種方法則是在創建 bean 實體時,通過推斷構造方法的方式來選擇使用帶有引數的構造方法進行實體化,

如果我們想要讓第四種方法生效,可以考慮使用多例的形式,即通過設定 scope 屬性為 prototype 來實作,這樣,每次獲取 bean 時,都會創建新的 bean 實體,從而可以觸發使用帶有引數的構造方法進行實體化,比如這樣:

@Component  
@Scope("prototype")  
public class UserService {  
  
     public UserService(){  
      System.out.println(0);  
   }  
     public UserService(OrderService orderService){  
      System.out.println(1);  
   }  
   public void test(){  
      System.out.println(11);  
   }  
}

getBean大體流程

由于方法代碼太多,我就不貼代碼了,我這邊只貼一些主要的偽代碼,方便大家閱讀,然后我在對每個流程細講下:

	protected <T> T doGetBean(
			String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
			throws BeansException {

		// name有可能是 &xxx 或者 xxx,如果name是&xxx,那么beanName就是xxx
		// name有可能傳入進來的是別名,那么beanName就是id
		String beanName = transformedBeanName(name);
		Object beanInstance;

		// Eagerly check singleton cache for manually registered singletons.
		Object sharedInstance = getSingleton(beanName);
		if (sharedInstance != null && args == null) {
			
			// 如果sharedInstance是FactoryBean,那么就呼叫getObject()回傳物件			
		}

		else {	
			//檢查是否本beanfactory沒有當前bean定義,查看有父容器,如果有,則呼叫父容器的getbean方法				  
			try {				 
				RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
				// 檢查BeanDefinition是不是Abstract的
				checkMergedBeanDefinition(mbd, beanName, args);
				// Guarantee initialization of beans that the current bean depends on.				 
				//查看是否有dependsOn注解,如果存在回圈依賴則報錯
				// Create bean instance.
				if (mbd.isSingleton()) {
					//呼叫createBean方法
					//如果是FactoryBean則呼叫getObject
				}
				else if (mbd.isPrototype()) {
					//呼叫createBean方法,與單例只是前后邏輯不一樣
					//如果是FactoryBean則呼叫getObject
				}
				else {
					Scope不同型別有不同實作
					//呼叫createBean方法,與單例只是前后邏輯不一樣
					//如果是FactoryBean則呼叫getObject
				}
			}catch{
				......
			}
		}

		// 檢查通過name所獲得到的beanInstance的型別是否是requiredType
		return adaptBeanInstance(name, beanInstance, requiredType);
	}

單例快取池

在 Spring 中,不管傳入的 beanName 是多例的還是單例的,都會先從單例快取池中獲取,有些人可能會覺得這樣做會浪費一些性能,但實際上 Spring 考慮到了大部分托管的 bean 都是單例的情況,因此忽略了這一點性能,實際上,這樣的性能消耗并不大,可以將其類比于 Java 的雙親委派機制,都會先查看本加載器是否有快取,如果沒有再向父加載器去加載,

parentBeanFactory

在分析 bean 定義是如何創建的時,我們可以不考慮單例快取池中獲取物件的情況,而是逐步分析 bean 定義是如何創建的,在這個程序中,即使存在 parentBeanFactory,我們也可以跳過它,因為我們的啟動容器并沒有設定任何父容器,原始碼也很簡單,如果本容器沒有 bean 定義,就直接呼叫父容器的 getBean 相關方法:

BeanFactory parentBeanFactory = getParentBeanFactory();
            if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
                // Not found -> check parent.
                // &&&&xxx---->&xxx
                String nameToLookup = originalBeanName(name);
                if (parentBeanFactory instanceof AbstractBeanFactory) {
                    return ((AbstractBeanFactory) parentBeanFactory).doGetBean(
                            nameToLookup, requiredType, args, typeCheckOnly);
                }
                else if (args != null) {
                    // Delegation to parent with explicit args.
                    return (T) parentBeanFactory.getBean(nameToLookup, args);
                }
                else if (requiredType != null) {
                    // No args -> delegate to standard getBean method.
                    return parentBeanFactory.getBean(nameToLookup, requiredType);
                }
                else {
                    return (T) parentBeanFactory.getBean(nameToLookup);
                }
            }

dependsOn

在呼叫 getBean 方法之前,已經將合并的 bean 定義存入了容器中,因此,我們可以直接獲取已經合并好的 bean 定義,并決議 bean 定義上的 dependsOn 注解,具體的原始碼邏輯如下:

RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);

                // 檢查BeanDefinition是不是Abstract的
                checkMergedBeanDefinition(mbd, beanName, args);

                // Guarantee initialization of beans that the current bean depends on.
                String[] dependsOn = mbd.getDependsOn();
                if (dependsOn != null) {
                    // dependsOn表示當前beanName所依賴的,當前Bean創建之前dependsOn所依賴的Bean必須已經創建好了
                    for (String dep : dependsOn) {
                        // beanName是不是被dep依賴了,如果是則出現了回圈依賴
                        if (isDependent(beanName, dep)) {
                            throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                                    "Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
                        }
                        // dep被beanName依賴了,存入dependentBeanMap中,dep為key,beanName為value
                        registerDependentBean(dep, beanName);

                        // 創建所依賴的bean
                        try {
                            getBean(dep);
                        }
                        catch (NoSuchBeanDefinitionException ex) {
                            throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                                    "'" + beanName + "' depends on missing bean '" + dep + "'", ex);
                        }
                    }
                }

這里的邏輯還是相對簡單的,如果當前 bean 被 dependsOn 注解所依賴,那么會先去創建所依賴的 bean,但是這種方式是解決不了回圈依賴的問題的,在實作上,只使用了兩個 Map 進行判斷:

// 某個Bean被哪些Bean依賴了
private final Map<String, Set<String>> dependentBeanMap = new ConcurrentHashMap<>(64);
// 某個Bean依賴了哪些Bean
private final Map<String, Set<String>> dependenciesForBeanMap = new ConcurrentHashMap<>(64);

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;
                        }
                    });
                    beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);

在這個階段,我們可以看到代碼已經在準備創建單例 bean 實體了,因此我們可以不去深入理解這部分的原始碼邏輯,反正,在 getSingleton 方法中,會呼叫 createBean 方法,這里使用了 lambda 運算式,如果有不太了解的讀者,可以參考下之前發的文章進行學習:

isPrototype

  if (mbd.isPrototype()) {
			// It's a prototype -> create a new instance.
			Object prototypeInstance = null;
			try {
				beforePrototypeCreation(beanName);
				prototypeInstance = createBean(beanName, mbd, args);
			}
			finally {
				afterPrototypeCreation(beanName);
			}
			beanInstance = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
		}

在這個階段,我們發現創建 bean 的方法已經改變了,直接呼叫了 createBean 方法,而不是通過 getSingleton 方法進行呼叫,至于 beforePrototypeCreation 和 afterPrototypeCreation,我們可以不用管它們,因為它們只是存盤一些資訊,對我們創建 bean 并沒有太大的影響,

其他Scope

講解這部分原始碼之前,我們先來看看還有哪些Scope域:

//@RequestScope
@SessionScope
public class User {
}

現在我們來看一下 RequestScope 和 SessionScope,它們與其他作用域類似,只是一個組合注解,它們的元注解資訊如下:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Scope(WebApplicationContext.SCOPE_SESSION)
public @interface SessionScope {

	/**
	 * Alias for {@link Scope#proxyMode}.
	 * <p>Defaults to {@link ScopedProxyMode#TARGET_CLASS}.
	 */
	@AliasFor(annotation = Scope.class)
	ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;

}

然后我們再來看下Spring對其他Scope注解的邏輯判斷:

String scopeName = mbd.getScope();
					if (!StringUtils.hasLength(scopeName)) {
						throw new IllegalStateException("No scope name defined for bean ′" + beanName + "'");
					}
					Scope scope = this.scopes.get(scopeName);
					if (scope == null) {
						throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
					}
					try {  // session.getAttriute(beaName)  setAttri
						Object scopedInstance = scope.get(beanName, () -> {
							beforePrototypeCreation(beanName);
							try {
								return createBean(beanName, mbd, args);
							}
							finally {
								afterPrototypeCreation(beanName);
							}
						});
						beanInstance = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
					}
					catch (IllegalStateException ex) {
						throw new ScopeNotActiveException(beanName, scopeName, ex);
					}

其實他主要用的getAttriute方法,我們看下scope.get主要的邏輯判斷:

	public Object get(String name, ObjectFactory<?> objectFactory) {
		RequestAttributes attributes = RequestContextHolder.currentRequestAttributes();
		Object scopedObject = attributes.getAttribute(name, getScope());
		if (scopedObject == null) {
			scopedObject = objectFactory.getObject();
			attributes.setAttribute(name, scopedObject, getScope());
			// Retrieve object again, registering it for implicit session attribute updates.
			// As a bonus, we also allow for potential decoration at the getAttribute level.
			Object retrievedObject = attributes.getAttribute(name, getScope());
			if (retrievedObject != null) {
				// Only proceed with retrieved object if still present (the expected case).
				// If it disappeared concurrently, we return our locally created instance.
				scopedObject = retrievedObject;
			}
		}
		return scopedObject;
	}

在這個階段,我們可以看到,通過 objectFactory.getObject() 方法,會呼叫外層定義的 lambda 運算式,也就是 createBean 方法的邏輯,假設這個程序成功地創建了 bean 實體,并回傳了它,那么 Spring 會呼叫 setAttribute 方法,將這個 bean 實體以及其 scope 值放入以 beanName 為 key 的屬性中,這樣,當需要獲取這個 bean 實體時,Spring 就可以直接從作用域中獲取了,

結語

getBean 方法主要包含以下幾個步驟:

  1. 首先,從單例快取池中獲取 bean 實體,如果沒有,Spring 會創建新的 bean 實體,并將其添加到單例快取池中,
  2. 接著,Spring 會檢查當前容器是否有指定名稱的 bean 定義,如果沒有,Spring 會呼叫父容器的 getBean 方法,直到找到為止,
  3. 一旦找到了 bean 定義,Spring 會根據不同的作用域型別,創建對應的 bean 實體,并將其存盤在作用域中,
  4. 最后,Spring 會回傳創建好的 bean 實體,

非常好,這樣我們對 getBean 方法的邏輯判斷有了一個大體的了解,有助于我們更好地理解 createBean 方法的實作細節,如果在后續的學習中有任何問題或疑問,可以隨時聯系我進行咨詢,

公眾號 ps:以上內容,純屬個人見解,有任何問題下方評論!關注博主公眾號,原始碼專題、面試精選、AI最新擴展等你來看!原創撰寫不易,轉載請說明出處!

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/551606.html

標籤:其他

上一篇:Java 雙指標專案中的實際應用

下一篇:返回列表

標籤雲
其他(158399) Python(38117) JavaScript(25399) Java(18012) C(15221) 區塊鏈(8261) C#(7972) AI(7469) 爪哇(7425) MySQL(7157) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5871) 数组(5741) R(5409) Linux(5334) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4565) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2432) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1964) Web開發(1951) HtmlCss(1931) python-3.x(1918) 弹簧靴(1913) C++(1912) xml(1889) PostgreSQL(1874) .NETCore(1857) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Spring原始碼:Bean生命周期(三)

    在之前的文章中,我們已經對 `bean` 的準備作業進行了講解,包括 `bean` 定義和 `FactoryBean` 判斷等。在這個基礎上,我們可以更加深入地理解 `getBean` 方法的實作邏輯,并在后續的學習中更好地掌握`createBean` 方法的實作細節。 ......

    uj5u.com 2023-05-05 08:18:35 more
  • Java 雙指標專案中的實際應用

    背景說明 最近在做財務相關的系統,對賬單核銷預付款從技術角度來看就是將兩個陣列進行合并 對賬單核銷預付款前提條件: 對賬單總金額必須等于未核銷金額 資料示例 對賬單資料 | 單號 | 金額 | | | | | B0001 | 100 | | B0002 | 80 | | B0003 | 120 | ......

    uj5u.com 2023-05-05 08:18:30 more
  • Java練手專案(尚硅谷的),不涉及框架,資料庫等。

    軟體:idea 我是先建立了一個空白的專案,自己創建的src包和其下面的包。 **問題一:**建立包之后發現格式為src.com.tjp.bean 沒辦法建立其他與bean同級的service test utils view 等。只允許繼續建立bean的子包。 解決: 這是因為idea自動會折疊空白 ......

    uj5u.com 2023-05-05 08:12:43 more
  • Python教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Python由荷蘭數學和計算機科學研究學會的吉多·范羅蘇姆于1990年代初設計,作為一門叫做ABC語言的替代品。 Python提供了高效的高級資料結構,還能簡單有效地面向物件編程。Python語法和動態型別,以及解釋型語言的本質,使它成為多數平臺上寫腳本和快速開發應用的編程語言, [2] ......

    uj5u.com 2023-05-05 08:06:50 more
  • JUC并發編程原理精講(原始碼分析)

    并發編程是指在程式中使用多執行緒技術來實作并行處理的能力。多執行緒機制使得程式可以分解成互不干擾的任務,從而提高了程式執行的效率。并發編程可以通過對執行緒的創建,管理和協作進行控制,以實作更加高效的并發執行。并發編程的優點包括:① 提高程式執行效率:通過多執行緒并行處理,程式的處理速度可以顯著提高。② 增強... ......

    uj5u.com 2023-05-05 08:00:35 more
  • 【pandas基礎】--資料讀取

    資料讀取是第一步,只有成功加載資料之后,后續的操作才有可能。 pandas可以讀取和匯入各種資料格式的資料,如CSV,Excel,JSON,SQL,HTML等,不需要手動撰寫復雜的讀取代碼。 1. 各類資料源 pandas提供了匯入各類常用檔案格式資料的介面,這里介紹3種最常用的加載資料的介面。 1 ......

    uj5u.com 2023-05-05 07:53:23 more
  • 一文吃透Tomcat核心知識點

    架構 首先,看一下整個架構圖。最全面的Java面試網站 接下來簡單解釋一下。 Server:服務器。Tomcat 就是一個 Server 服務器。 Service:在服務器中可以有多個 Service,只不過在我們常用的這套 Catalina 容器的Tomcat 中只包含一個 Service,在 S ......

    uj5u.com 2023-05-05 07:52:47 more
  • SpringBoot匯出Word檔案的三種方式

    SpringBoot匯出Word檔案的三種方式 一、匯出方案 1、直接在Java代碼里創建Word檔案,設定格式樣式等,然后匯出。(略) 需要的見:https://blog.csdn.net/qq_42682745/article/details/120867432 2、富文本轉換后的HTML下載為 ......

    uj5u.com 2023-05-05 07:52:09 more
  • golang推薦的命名規范

    二 golang推薦的命名規范 很少見人總結一些命名規范,也可能是筆者孤陋寡聞, 作為一個兩年的golang 開發者, 我根據很多知名的專案,如 moby, kubernetess 等總結了一些常見的命名規范。 命名規范可以使得代碼更容易與閱讀, 更少的出現錯誤。 檔案命名規范 由于檔案跟包無任何關 ......

    uj5u.com 2023-05-05 07:51:51 more
  • golang基礎知識

    一 golang基礎知識 Go(又稱 Golang)是 Google 的 Robert Griesemer,Rob Pike 及 Ken Thompson 開發的一種計算機編程語言語言。 設計初衷 Go語言是谷歌推出的一種的編程語言,可以在不損失應用程式性能的情況下降低代碼的復雜性。谷歌首席軟體工程 ......

    uj5u.com 2023-05-05 07:51:41 more