首頁 > 軟體

Spring Bean生命週期之Bean的註冊詳解

2022-03-04 16:00:34

前言

上篇文章介紹了Bean元資訊的設定與解析過程,限於篇幅Bean註冊過程就沒展開。

這裡主要圍繞BeanDefinitionReaderUtils#registerBeanDefinition展開分析下Bean註冊過程

public static void registerBeanDefinition(
			BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
			throws BeanDefinitionStoreException {
		// Register bean definition under primary name.
		String beanName = definitionHolder.getBeanName();
		registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
		// Register aliases for bean name, if any.
		String[] aliases = definitionHolder.getAliases();
		if (aliases != null) {
			for (String alias : aliases) {
				registry.registerAlias(beanName, alias);
			}
		}
	}

上面無論是註冊bd還是建立alias-beanName之間的關係,均用到了BeanDefinitionRegistry,因此我們就以它為突破口來展開

BeanFactory的繼承體系

對圖中常用介面或類進行說明:

  • ListableBeanFactory 集合型別BeanFactory 提供一種可以查詢所有Bean範例的能力
    • getBeanNamesForType(Class) 根據型別去查詢Bean名稱列表不會強制Bean的初始化,可從原始碼中看出來
    • getBeansOfType(Class) 根據型別去查詢Bean範例列表,會強制Bean的初始化,可從原始碼中看出來
    • getBeanNamesForAnnotation(Class) 根據註解型別獲取Bean名稱列表
    • getBeansWithAnnotation(Class) 根據註解型別獲取Bean範例列表
    • findAnnotationOnBean(String,Class) 根據指定名稱+標註型別獲取Bean範例
  • Hierarchical([ˌhaɪəˈrɑːkɪkl])BeanFactory 層次性BeanFactory,有父子容器的概念,可在ConfigurableListableBeanFactory設定其父容器
    • getParentBeanFactory() 獲取父容器
    • boolean containsLocalBean(String name) 在當前容器中查詢是否存在該名稱的Bean範例
  • SingletonBeanRegistry 單範例BeanFactory,與單範例有關
  • ConfigurableBeanFactory 可設定的BeanFactory,這個一般不用於應用程式,是給其他BeanFactory擴充套件用的。的確,定義了很多設定方法
  • ConfigurableListableBeanFactory 可設定的集合型別的BeanFactory
  • AutowireCapableBeanFactory 提供具有自動裝配能力的BeanFactory

透過繼承體系可以看出,BeanDefinitionRegistry的實現類是DefaultListableBeanFactory,該類同時實現了諸多介面,可謂是BeanFactory中集大成者,因此我們到DefaultListableBeanFactory中閱讀下bd註冊及別名註冊的原始碼

Bean的註冊

先來分析下DefaultListableBeanFactory的幾個重要的成員屬性

// 這個實質上就是IoC容器中Bean的載體,沒錯 它很重要,但它是無序的
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);
//它代表了bd名稱的集合,它是有序的 遵循bd註冊的順序
private volatile List<String> beanDefinitionNames = new ArrayList<>(256);
// 這是已建立bd名稱的集合,在doGetBean方法根據beanName建立Bean時,beanName會被加到此集合中
private final Set<String> alreadyCreated = Collections.newSetFromMap(new ConcurrentHashMap<>(256));

上面兩個屬性都比較重要,兩者結合使用的話可以實現bd的順序存取(其實就是遍歷beanDefinitionNames集合時,使用beanDefinitionMap去獲取bd)

public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
			throws BeanDefinitionStoreException {
		//對beanName、bd進行非空驗證
		Assert.hasText(beanName, "Bean name must not be empty");
		Assert.notNull(beanDefinition, "BeanDefinition must not be null");
		//如果bd是AbstractBeanDefinition型別,則對bd進行驗證(一般情況下 我們場景的bd都是繼承自AbstractBeanDefinition的)
		if (beanDefinition instanceof AbstractBeanDefinition) {
			try {
			   //bd驗證
				((AbstractBeanDefinition) beanDefinition).validate();
			}
			catch (BeanDefinitionValidationException ex) {
				//省略異常程式碼
			}
		}
		//從beanDefinitionMap根據beanName取bd
		BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
		//如果beanName名稱的bd已經存在
		if (existingDefinition != null) {
			//如果不允許Bean被重新註冊 則丟擲異常,這裡預設值是true
			if (!isAllowBeanDefinitionOverriding()) {
				throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);
			}
			//如果已被註冊bd的角色值小於當前待註冊bd的角色值 
			else if (existingDefinition.getRole() < beanDefinition.getRole()) {
				// 省略紀錄檔輸出
			}
			//如果已註冊的同名bd 與本次註冊的bd不相同
			else if (!beanDefinition.equals(existingDefinition)) {
				//省略紀錄檔輸出
			}
			else {
				//省略紀錄檔輸出
			}
			//將beanName-bd鍵值對放入beanDefinitionMap集合
			this.beanDefinitionMap.put(beanName, beanDefinition);
		}
		else {
			//流程走到這裡 說明在beanDefinitionMap中不存在同名bd
			//條件成立 說明alreadyCreated不為空 即有bd已被建立
			if (hasBeanCreationStarted()) {
				// 如果在此之間 有bean正在被建立 則這裡進行加鎖處理
				synchronized (this.beanDefinitionMap) {
				    //將beanName-bd鍵值對放入beanDefinitionMap集合
					this.beanDefinitionMap.put(beanName, beanDefinition);
					//將beanName新增到beanDefinitionNames集合中
					List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
					updatedDefinitions.addAll(this.beanDefinitionNames);
					updatedDefinitions.add(beanName);
					this.beanDefinitionNames = updatedDefinitions;
					//如果beanName是手動註冊的單例Bean名稱,則更新manualSingletonNames
					if (this.manualSingletonNames.contains(beanName)) {
						Set<String> updatedSingletons = new LinkedHashSet<>(this.manualSingletonNames);
						//這裡從集合中刪除的原因個人理解:
						//manualSingletonNames記錄的是在registerSingleton時被新增的單範例beanName,而這裡注入的不是單範例Bean。因為manualSingletonNames包含了此beanName,因此需要剔除
						updatedSingletons.remove(beanName);
						this.manualSingletonNames = updatedSingletons;
					}
				}
			}
			else {
				//如果沒有bean在被建立
				//將beanName-bd鍵值對放入beanDefinitionMap集合
				this.beanDefinitionMap.put(beanName, beanDefinition);
				//將beanName新增到集合中
				this.beanDefinitionNames.add(beanName);
				//這裡從manualSingletonNames中剔除,個人理解為拖地操作,畢竟若集合中沒有此beanName 也remove不了
				this.manualSingletonNames.remove(beanName);
			}
			//這個集合表示凍結設定時快取的beanName集合,暫時未理解透此集合的用途
			this.frozenBeanDefinitionNames = null;
		}
		//如果已存在同名bd或已存在同名的單例物件,則重置所有已被快取的同名的bd資料,因此這裡bd註冊成功後,肯定後續還會再生成Bean的
		if (existingDefinition != null || containsSingleton(beanName)) {
			resetBeanDefinition(beanName);
		}
	}

其實分析下來發現Bean註冊的過程還是比較容易理解的,下面試著總結一下:

  • 若bd未被註冊過,則將bd資訊存入BeanDefinitionMap等集合中
  • 若bd已被註冊過,允許覆蓋註冊的情況下,將bd資訊存入BeanDefinitionMap等集合中,並清除已被快取的同名bd資訊

下面看一下清除bd資訊的程式碼邏輯

protected void resetBeanDefinition(String beanName) {
		// 如果此bd屬於被合併的BeanDefinition,則這裡將其從MergeBeanDefinition集合中剔除
		clearMergedBeanDefinition(beanName);
		// 如果已存在同名的單例物件 則銷燬,具體細節先不展開
		destroySingleton(beanName);
		// 這裡for迴圈邏輯與MergeBeanDefinition相關,如果存在MergedBeanDefinitionPostProcessor,則重置此bd
		for (BeanPostProcessor processor : getBeanPostProcessors()) {
			if (processor instanceof MergedBeanDefinitionPostProcessor) {
				((MergedBeanDefinitionPostProcessor) processor).resetBeanDefinition(beanName);
			}
		}
		// BeanDefinition執行有層級的,如果此bd擁有多個父級bd,那麼這裡遞迴地重置其父bd
		for (String bdName : this.beanDefinitionNames) {
			if (!beanName.equals(bdName)) {
				BeanDefinition bd = this.beanDefinitionMap.get(bdName);
				if (beanName.equals(bd.getParentName())) {
					resetBeanDefinition(bdName);
				}
			}
		}
	}

alias別名的註冊

看了Bean的註冊,再來看別名的註冊 發現流程比較清晰,基本上一目瞭然。

//注意 這裡的name 不要具象為beanName,雖然我們是從建立beanName--alias關係出發追溯到這裡的
public void registerAlias(String name, String alias) {
		//對name、alias進行斷言驗證
		Assert.hasText(name, "'name' must not be empty");
		Assert.hasText(alias, "'alias' must not be empty");
		synchronized (this.aliasMap) {
			//如果別名與beanName相同,那別名就沒有必要存在了,因此選擇直接從this.aliasMap中移除此別名
			if (alias.equals(name)) {
				this.aliasMap.remove(alias);
				//省略紀錄檔輸出
			}
			else {
			   //從aliasMap中根據別名獲取name
				String registeredName = this.aliasMap.get(alias);
				if (registeredName != null) {
				    //如果已存在的registeredName與此此要註冊的name一致,那就沒必要註冊了
					if (registeredName.equals(name)) {
						return;
					}
					//流程走到這裡,說明同一個別名,對應兩個name,如果不允許alias覆蓋 則丟擲異常
					if (!allowAliasOverriding()) {
						//省略異常及紀錄檔輸出
				}
				//這裡對alias進行迴圈檢查,避免出現A的別名是B,B的別名是A的情況
				checkForAliasCircle(name, alias);
				//將alias--name 放入aliasMap
				this.aliasMap.put(alias, name);
				//省略紀錄檔輸出
			}
		}
	}

alias與beanName的對映關係,為根據名稱查詢Bean又提供了一種思路。就是說除了根據beanName外,也可以根據alias去查詢Bean。

這部分原始碼如下

//name可以是beanName,也可以是alias
public String canonicalName(String name) {
		//區域性變數賦值
		String canonicalName = name;
		// Handle aliasing...
		String resolvedName;
		do {
		    //如果從aliasMap中能根據alias分析出beanName
			resolvedName = this.aliasMap.get(canonicalName);
			if (resolvedName != null) {
				canonicalName = resolvedName;
			}
		}
		while (resolvedName != null);
		// 無論入參name是beanName還是alias,這裡返回的都應該是beanName了
		return canonicalName;
	}

總結

好了,這篇主要分析了BeanDefinition的註冊,順帶著也說了別名的註冊情況。既然BeanDefinition已經註冊完成,那緊接著就是BeanDefinition的範例化過程了,這個放到下次分析吧。

本篇文章就到這裡了,希望能夠給你帶來幫助,也希望您能夠多多關注it145.com的更多內容!     


IT145.com E-mail:sddin#qq.com