Spring 源碼學習(四) bean 的加載

解析完配置後,來看下 bean 是如何加載的java


前言

既然咱們 Spring 辛辛苦苦將 bean 進行了註冊,固然須要拿出來進行使用,在使用以前還須要通過一個步驟,就是 bean 的加載。git

在第一篇筆記提到了,完成 bean 註冊到 beanDefinitionMap 註冊表後,還調用了不少後處理器的方法,其中有一個方法 finishBeanFactoryInitialization(),註釋上面寫着 Instantiate all remaining (non-lazy-init) singletons,意味着非延遲加載的類,將在這一步實例化,完成類的加載。github

而咱們使用到 context.getBean("beanName")方法,若是對應的 bean 是非延遲加載的,那麼直接就能拿出來進行使用,而延遲加載的 bean 就須要上面的步驟進行類的加載,加載完以後才能進行使用~spring

下面一塊兒來看下這兩個步驟中, bean 是如何進行加載的。緩存


時序圖

咱們的代碼分析都是圍繞着這個方法,請同窗們提早定位好位置:bash

org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean多線程

這個 bean 加載的代碼量是有點多的,已經超過 100 行,因此整理了時序圖,但願能對加載流程有個清晰的概覽:併發

這個時序圖介紹了 bean 加載的大致流程,還有不少細節沒在圖中進行展現。咱們先對總體流程有個瞭解,而後跟着代碼一塊兒深刻分析吧。mvc


代碼分析

再提示一下:因爲代碼量不少,每次貼大段代碼看起來會比較吃力,因此展現的是我認爲的關鍵代碼,下載項目看完整註釋,跟着源碼一塊兒分析~app

碼雲 Gitee 地址

Github 地址


FactoryBean 的使用

在分析加載流程以前,有個前置概念要了解下,在通常狀況下,Spring 是經過反射機制利用 beanclass 屬性指定實現類來實例化 bean

引用書本:

在某些狀況下,實例化 bean 比較複雜,例若有多個參數的狀況下,傳統方式須要在配置文件中,寫不少配置信息,顯得不太靈活。在這種狀況下,可使用 Spring 提供的 FactoryBean 接口,用戶能夠經過實現該接口定製實例化 bean 的邏輯。

FactoryBean 接口定義了三個方法:

public interface FactoryBean<T> {
	T getObject() throws Exception;
	Class<?> getObjectType();
	default boolean isSingleton() {
		return true;
	}
}
複製代碼

主要講下用法吧:

當配置文件中的 <bean>class 屬性實現類是 FactoryBean 時,經過 getBean() 方法返回的不是 FactoryBean 自己,而是 FactoryBean#getObject() 方法返回的對象。

使用 demo 代碼請看下圖:

擴展 FactoryBean 以後,須要重載圖中的兩個方法,經過泛型約定返回的類型。在重載的方法中,進行本身個性化的處理。

在啓動類 Demo,經過上下文獲取類的方法 context.getBean("beanName"),使用區別是 beanName 是否使用 & 前綴,若是有沒有 & 前綴,識別到的是 FactoryBean.getObject 返回的 car 類型,若是帶上 & 前綴,那麼將會返回 FactoryBean 類型的類。

驗證和學習書中的概念,最快的方式是運行一遍示例代碼,看輸出結果是否符合預期,因此參考書中的例子,本身手打代碼,看最後的輸出結果,發現與書中說的一致,同時也加深了對 FactoryBean 的瞭解。


爲何要先講 BeanFactory 這個概念呢?

從時序圖看,在 1.5 那個步驟,調用了方法:

org.springframework.beans.factory.support.AbstractBeanFactory#getObjectForBeanInstance

在這一步中,會判斷 sharedInstance 類型,若是屬於 FactoryBean,將會調用用戶自定義 FactoryBeangetObject() 方法進行 bean 初始化。

實例化的真正類型是 getObjectType() 方法定義的類型,不是 FactoryBean 原來自己的類型。最終在容器中註冊的是 getObject() 返回的 bean

提早講了這個概念,但願你們在最後一步時不會對這個有所迷惑。


從緩存中獲取單例 bean

// Eagerly check singleton cache for manually registered singletons.
// 檢查緩存中或者實例工廠是否有對應的實例或者從 singletonFactories 中的 ObjectFactory 中獲取
Object sharedInstance = getSingleton(beanName);

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
	Object singletonObject = this.singletonObjects.get(beanName);
	// 檢查緩存中是否存在實例
	if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
		// 記住,公共變量都須要加鎖操做,避免多線程併發修改
		synchronized (this.singletonObjects) {
			// 若是此 bean 正在加載則不處理
			singletonObject = this.earlySingletonObjects.get(beanName);
			if (singletonObject == null && allowEarlyReference) {
				// 當某些方法須要提早初始化,調用 addSingletonFactory 方法將對應的
				// objectFactory 初始化策略存儲在 singletonFactories
				ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
				if (singletonFactory != null) {
					singletonObject = singletonFactory.getObject();
					this.earlySingletonObjects.put(beanName, singletonObject);
					this.singletonFactories.remove(beanName);
				}
			}
		}
	}
	return singletonObject;
}
複製代碼

單例模式在代碼設計中常常用到,在 Spring 中,同一個容器的單例只會被建立一次,後續再獲取 bean 直接從單例緩存 singletonObjects 中進行獲取。

並且由於單例緩存是公共變量,因此對它進行操做的時候,都進行了加鎖操做,避免了多線程併發修改或讀取的覆蓋操做。

還有這裏有個 earlySingletonObjects 變量,它也是單例緩存,也是用來保存 beanName 和 建立 bean 實例之間的關係。

singletonFactories 不一樣的是,當一個單例 bean 被放入到這 early 單例緩存後,就要從 singletonFactories 中移除,二者是互斥的,主要用來解決循環依賴的問題。(循環依賴下一篇再詳細講吧)


從 bean 的實例中獲取對象

getBean 方法中,getObjectForBeanInstance 是個高頻方法,在單例緩存中得到 bean 仍是 根據不一樣 scope 策略加載 bean,都有這個方法的出現,因此結合剛纔說的 BeanFactory 概念,一塊兒來看下這個方法作了什麼。

org.springframework.beans.factory.support.AbstractBeanFactory#getObjectForBeanInstance

// 返回對應的實例,有時候存在諸如 BeanFactory 的狀況並非直接返回實例自己
// 而是返回指定方法返回的實例
bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
複製代碼

具體方法實現,搜索 註釋 4.6 看代碼中的註釋吧:

get_object_from_factory_bean

交代一下這個方法的流程:

  • 驗證 bean 類型:判斷是不是工廠 bean
  • 對非 FactoryBean 不作處理
  • bean 進行轉換
  • 處理 FactoryBean 類型:委託給 getObjectFromFactoryBean 方法進行處理。

在這個方法中,對工廠 bean 有特殊處理,處理方法跟上面提到的 FactoryBean 使用同樣,最終獲取的是 FactoryBean.getObject() 方法返回的類型。

對於第四個步驟,委託給 getObjectFromFactoryBean 方法進行處理不深刻分析,但裏面有三個方法值得一說:

// 單例操做,前置操做
beforeSingletonCreation(beanName);
try {
	object = postProcessObjectFromFactoryBean(object, beanName);
}
catch (Throwable ex) {
	throw new BeanCreationException(beanName,
			"Post-processing of FactoryBean's singleton object failed", ex);
}
finally {
	// 單例模式,後置操做
	afterSingletonCreation(beanName);
}
複製代碼

代碼中在類的加載時,有前置操做和後置操做,以前在第一篇筆記看過,不少前置和後置操做都是空方法,等用戶自定義擴展用的。

但在這裏的不是空方法,在兩個方法是用來保存和移除類加載的狀態,是用來對循環依賴進行檢測的。

同時,這兩個方法在不一樣 scope 加載 bean 時也有使用到,也是個高頻方法。

try {
	object = postProcessObjectFromFactoryBean(object, beanName);
}
catch (Throwable ex) {
	throw new BeanCreationException(beanName, "Post-processing of FactoryBean's object failed", ex);
}
複製代碼

這是一個執行後處理的方法,我接觸的很少,先記下概念:

Spring 獲取 bean 的規則中有一條:儘量保證全部 bean 初始化後都會調用註冊的 BeanPostProcessor 的 postProcessAfterInitialization 方法進行處理。在實際開發中,能夠針對這個特性進行擴展。


獲取單例

如今來到時序圖中的 1.3 步驟:

// Create bean instance. 建立 bean 實例
// singleton 單例模式(最常使用)
if (mbd.isSingleton()) {
    // 第二個參數的回調接口,接口是 org.springframework.beans.factory.ObjectFactory#getObject
    // 接口實現的方法是 createBean(beanName, mbd, args)
	sharedInstance = getSingleton(beanName, () -> {
		return createBean(beanName, mbd, args);
		// 省略了 try / catch
	});
	bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
複製代碼

來看 getSingleton 方法作了什麼:

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
	Assert.notNull(beanName, "Bean name must not be null");
	// 註釋 4.7 全局變量,加鎖
	synchronized (this.singletonObjects) {
		// 檢查是否已經被加載了,單例模式就是能夠複用已經建立的 bean
		Object singletonObject = this.singletonObjects.get(beanName);
		if (singletonObject == null) {
			// 初始化前操做,校驗是否 beanName 是否有別的線程在初始化,並加入初始化狀態中
			beforeSingletonCreation(beanName);
			boolean newSingleton = false;
			boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
			if (recordSuppressedExceptions) {
				this.suppressedExceptions = new LinkedHashSet<>();
			}
			// 初始化 bean,這個就是剛纔的回調接口調用的方法,實際執行的是 createBean 方法
			singletonObject = singletonFactory.getObject();
			newSingleton = true;
			if (recordSuppressedExceptions) {
				this.suppressedExceptions = null;
			}
			// 初始化後的操做,移除初始化狀態
			afterSingletonCreation(beanName);
			if (newSingleton) {
				// 加入緩存
				addSingleton(beanName, singletonObject);
			}
		}
		return singletonObject;
	}
}
複製代碼

來梳理一下流程:

  • 檢查緩存是否已經加載過
  • 沒有加載,記錄 beanName 的加載狀態
  • 調用回調接口,實例化 bean
  • 加載單例後的處理方法調用:這一步就是移除加載狀態
  • 將結果記錄到緩存並刪除加載 bean 過程當中所記錄到的各類輔助狀態

對於第二步和第四步,在前面已經提到,用來記錄 bean 的加載狀態,是用來對 循環依賴 進行檢測的,這裏先略過不說。

關鍵的方法在於第三步,調用了 ObjectFactorygetObject() 方法,實際回調接口實現的是 createBean() 方法,須要往下了解,探祕 createBean()


準備建立 bean

對於書中,有句話說的很到位:

Spring 源碼中,一個真正幹活的函數實際上是以 do 開頭的,好比 doGetBeandoGEtObjectFromFactoryBean,而入口函數,好比 getObjectFromFactoryBean,實際上是從全局角度去作統籌工做。

有了這個概念後,看以後的 Spring 源碼,都知道這個套路,在入口函數了解總體流程,而後重點關注 do 開頭的幹活方法。

按照這種套路,咱們來看這個入口方法 createBean()

org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBean(java.lang.String, org.springframework.beans.factory.support.RootBeanDefinition, java.lang.Object[])

protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {
	RootBeanDefinition mbdToUse = mbd;
	// 有道翻譯:確保此時bean類已經被解析,而且克隆 bean 定義,以防動態解析的類不能存儲在共享合併 bean 定義中。
	// 鎖定 class,根據設置的 class 屬性或者根據 className 來解析 Class
	Class<?> resolvedClass = resolveBeanClass(mbd, beanName);
	if (resolvedClass != null && !mbd.hasBeanClass() && mbd.getBeanClassName() != null) {
		mbdToUse = new RootBeanDefinition(mbd);
		mbdToUse.setBeanClass(resolvedClass);
	}
	// Prepare method overrides.
	// 驗證及準備覆蓋的方法
    mbdToUse.prepareMethodOverrides();
    // 讓 beanPostProcessor 有機會返回代理而不是目標bean實例。
    Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
    if (bean != null) {
    	// 短路操做,若是代理成功建立 bean 後,直接返回
    	return bean;
    }
	
	// 建立 bean
	Object beanInstance = doCreateBean(beanName, mbdToUse, args);
	return beanInstance;
}
複製代碼

先來總結這個流程:

  • 根據設置的 class 屬性或者根據 className 來解析 Class
  • 驗證及準備覆蓋的方法 這個方法是用來處理如下兩個配置的:咱們在解析默認標籤時,會識別 lookup-methodreplaced-method 屬性,而後這兩個配置的加載將會統一存放在 beanDefinition 中的 methodOverrides 屬性裏。
  • 應用初始化前的後處理器,解析指定 bean 是否存在初始化前的短路操做
  • 建立 bean

下面來說下這幾個主要步驟


處理 Override 屬性

public void prepareMethodOverrides() throws BeanDefinitionValidationException {
	// Check that lookup methods exists.
	if (hasMethodOverrides()) {
		Set<MethodOverride> overrides = getMethodOverrides().getOverrides();
		synchronized (overrides) {
			for (MethodOverride mo : overrides) {
				// 處理 override 屬性
				prepareMethodOverride(mo);
			}
		}
	}
}
複製代碼

能夠看到,獲取類的重載方法列表,而後遍歷,一個一個進行處理。具體處理的是 lookup-methodreplaced-method 屬性,這個步驟解析的配置將會存入 beanDefinition 中的 methodOverrides 屬性裏,是爲了待會實例化作準備。

若是 bean 在實例化時,監測到 methodOverrides 屬性,會動態地位當前 bean 生成代理,使用對應的攔截器爲 bean 作加強處理。

(我是不推薦在業務代碼中使用這種方式,定位問題和調用都太麻煩,一不當心就會弄錯=-=)


實例化前的前置處理

// 讓 beanPostProcessor 有機會返回代理而不是目標bean實例。
Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
if (bean != null) {
	// 短路操做,若是代理成功建立 bean 後,直接返回
	return bean;
}

protected Object resolveBeforeInstantiation(String beanName, RootBeanDefinition mbd) {
	Object bean = null;
	if (!Boolean.FALSE.equals(mbd.beforeInstantiationResolved)) {
		// Make sure bean class is actually resolved at this point.
		if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
			Class<?> targetType = determineTargetType(beanName, mbd);
			if (targetType != null) {
			    // 執行前置攔截器的操做
				bean = applyBeanPostProcessorsBeforeInstantiation(targetType, beanName);
				if (bean != null) {
				    // 執行後置攔截器的操做
					bean = applyBeanPostProcessorsAfterInitialization(bean, beanName);
				}
			}
		}
		mbd.beforeInstantiationResolved = (bean != null);
	}
	return bean;
}
複製代碼

doCreateBean 方法前,有一個短路操做,若是後處理器成功,將會返回代理的 bean

resolveBeforeInstantiation 方法中,在確保 bean 信息已經被解析完成,執行了兩個關鍵方法,從註釋中看到,一個是前置攔截器的操做,另外一個就是後置攔截器的操做。

若是第一個前置攔截器實例化成功,就已經將單例 bean 放入緩存中,它不會再經歷普通 bean 的建立過程,沒有機會進行後處理器的調用,因此在這裏的第二個步驟,就是爲了這個 bean 也能應用後處理器的 postProcessAfterInitialization 方法。


建立 bean

終於到了關鍵的幹活方法:doGetBean。在經過上一個方法校驗,沒有特定的前置處理,因此它是一個普通 bean, 常規 bean 進行建立在 doGetBean 方法中完成。

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args) {
	// Instantiate the bean.
	BeanWrapper instanceWrapper = null;
	if (mbd.isSingleton()) {
		instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
	}
	if (instanceWrapper == null) {
		// 註釋 4.8 根據指定 bean 使用對應的策略建立新的實例 例如跟進方法去看,有工廠方法,構造函數自動注入,簡單初始化
		instanceWrapper = createBeanInstance(beanName, mbd, args);
	}
	final Object bean = instanceWrapper.getWrappedInstance();
	Class<?> beanType = instanceWrapper.getWrappedClass();
	if (beanType != NullBean.class) {
		mbd.resolvedTargetType = beanType;
	}
	// 容許後處理程序修改合併的bean定義
	synchronized (mbd.postProcessingLock) {
		if (!mbd.postProcessed) {
			applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
			mbd.postProcessed = true;
		}
	}
	// 是否須要提早曝光,用來解決循環依賴時使用
	boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
			isSingletonCurrentlyInCreation(beanName));
	if (earlySingletonExposure) {
		// 第二個參數是回調接口,實現的功能是將切面動態織入 bean
		addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
	}
	Object exposedObject = bean;
    // 對 bean 進行填充,將各個屬性值注入
    // 若是存在對其它 bean 的依賴,將會遞歸初始化依賴的 bean
    populateBean(beanName, mbd, instanceWrapper);
    // 調用初始化方法,例如 init-method
    exposedObject = initializeBean(beanName, exposedObject, mbd);
	
	if (earlySingletonExposure) {
		Object earlySingletonReference = getSingleton(beanName, false);
		// earlySingletonReference 只有在檢測到有循環依賴的狀況下才 不爲空
		if (earlySingletonReference != null) {
			if (exposedObject == bean) {
				// 若是 exposedObject 沒有在初始化方法中被改變,也就是沒有被加強
				exposedObject = earlySingletonReference;
			}
			else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
				String[] dependentBeans = getDependentBeans(beanName);
				Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
				for (String dependentBean : dependentBeans) {
					// 檢查依賴
					if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
						actualDependentBeans.add(dependentBean);
					}
				}
				// bean 建立後,它所依賴的 bean 必定是已經建立了
				// 在上面已經找到它有依賴的 bean,若是 actualDependentBeans 不爲空
				// 表示還有依賴的 bean 沒有初始化完成,也就是存在循環依賴
				if (!actualDependentBeans.isEmpty()) {
					throw new BeanCurrentlyInCreationException(beanName);
			}
		}
	}
	// Register bean as disposable.
	// 根據 scope 註冊 bean
	registerDisposableBeanIfNecessary(beanName, bean, mbd);
	return exposedObject;
}
複製代碼

看到這麼長的代碼,感受有點頭暈,因此先來梳理這個方法的流程:

  1. 若是加載的 bean 是單例,要清除緩存
  2. 實例化 bean,將 BeanDifinition 轉化成 BeanWrapper
  3. 後處理器修改合併後的 bean 定義: bean 合併後的處理,Autowired 註解正式經過此方法實現諸如類型的預解析
  4. 依賴處理
  5. 屬性填充:將全部屬性填充到 bean 的實例中
  6. 循環依賴檢查
  7. 註冊 DisposableBean:這一步是用來處理 destroy-method 屬性,在這一步註冊,以便在銷燬對象時調用。
  8. 完成建立並返回

從上面流程能夠看出,這個方法作了不少事情,以致於代碼超過了 100 多行,給人的閱讀體驗差,因此儘可能仍是拆分小方法,在入口方法儘可能簡潔,說明作的事情,具體在小方法中完成。

由於這個建立過程的代碼不少和複雜,我挑重點來理解和學習,詳細的還有待深刻學習


建立 bean 的實例

在上面第二個步驟,作的是實例化 bean,而後返回 BeanWrapper

protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, Object[] args) {
	// Make sure bean class is actually resolved at this point.
	Class<?> beanClass = resolveBeanClass(mbd, beanName);
	Supplier<?> instanceSupplier = mbd.getInstanceSupplier();
	// Shortcut when re-creating the same bean...
	boolean resolved = false;
	boolean autowireNecessary = false;
	if (args == null) {
		synchronized (mbd.constructorArgumentLock) {
			// 若是一個類有多個構造函數,每一個構造函數都有不一樣的參數,調用前須要進行判斷對應的構造函數或者工廠方法
			if (mbd.resolvedConstructorOrFactoryMethod != null) {
				resolved = true;
				autowireNecessary = mbd.constructorArgumentsResolved;
			}
		}
	}
	// 若是已經解析過,不須要再次解析
	if (resolved) {
		if (autowireNecessary) {
			// 實際解析的是 org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor
			// 構造函數自動注入(若是參數有不少個,在匹配構造函數可複雜了,不敢細看=-=)
			return autowireConstructor(beanName, mbd, null, null);
		}
		else {
			// 使用默認的構造函數
			return instantiateBean(beanName, mbd);
		}
	}
    // Candidate constructors for autowiring? 須要根據參數解析構造函數
	Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
	if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR ||
			mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {
		return autowireConstructor(beanName, mbd, ctors, args);
	}
	// Preferred constructors for default construction?
	ctors = mbd.getPreferredConstructors();
	if (ctors != null) {
		// 構造函數注入
		return autowireConstructor(beanName, mbd, ctors, null);
	}
	// No special handling: simply use no-arg constructor. 沒有特殊的處理,使用默認構造函數構造
	return instantiateBean(beanName, mbd);
}
複製代碼

大體介紹功能:

  • 若是存在工廠方法則使用工廠方法進行初始化
  • 一個類有多個構造函數,每一個構造函數都有不一樣的參數,因此須要根據參數鎖定構造函數進行 bean 的實例化: 在這一步我是真心服,爲了匹配到特定的構造函數,下了很大的功夫,感興趣的能夠定位到這個函數觀看 org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor
  • 若是即不存在工廠方法,也不存在帶有參數的構造函數,會使用默認的構造函數進行 bean 的實例化

在這個流程中,經過兩種方式,一種是工廠方法,另外一種就是構造函數,將傳進來的 RootBeanDefinition 中的配置二選一輩子成 bean 實例

具體的不往下跟蹤,來看下一個步驟


處理循環依賴

// 是否須要提早曝光,用來解決循環依賴時使用
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
		isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
	// 第二個參數是回調接口,實現的功能是將切面動態織入 bean
	addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
複製代碼

關鍵方法是 addSingletonFactory,完成的做用:在 bean 初始化完成前將建立實例的 ObjectFactory 加入單例工廠

一開始就講過, ObjectFactory 是建立對象時使用的工廠。在對象實例化時,會判斷本身依賴的對象是否已經建立好了,判斷的依據是查看依賴對象的 ObjectFactory 是否在單例緩存中,若是沒有建立將會先建立依賴的對象,而後將 ObjectFactory 放入單例緩存。

這時若是有循環依賴,須要提早對它進行暴露,讓依賴方找到並正常實例化。

循環依賴解決方案在下一篇再細講吧。


屬性注入

這也是個高頻方法,在初始化的時候要對屬性 property 進行注入,貼一些代碼片斷:

populateBean(beanName, mbd, instanceWrapper);

protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {
	// 給 awareBeanPostProcessor 後處理器最後一次機會,在屬性設置以前修改bean的屬性
	boolean continueWithPropertyPopulation = true;
	if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
		...
        if (!ibp.postProcessAfterInstantiation(bw.getWrappedInstance(), beanName)) {
			continueWithPropertyPopulation = false;
			break;
		}
        ...
	}
	PropertyValues pvs = (mbd.hasPropertyValues() ? mbd.getPropertyValues() : null);
	int resolvedAutowireMode = mbd.getResolvedAutowireMode();
	if (resolvedAutowireMode == AUTOWIRE_BY_NAME || resolvedAutowireMode == AUTOWIRE_BY_TYPE) {
		MutablePropertyValues newPvs = new MutablePropertyValues(pvs);
		// Add property values based on autowire by name if applicable.
		if (resolvedAutowireMode == AUTOWIRE_BY_NAME) {
			// 根據名字自動注入
			autowireByName(beanName, mbd, bw, newPvs);
		}
		// Add property values based on autowire by type if applicable.
		if (resolvedAutowireMode == AUTOWIRE_BY_TYPE) {
			// 根據類型自動注入
			autowireByType(beanName, mbd, bw, newPvs);
		}
		pvs = newPvs;
	}
	// 後處理器已經初始化
	boolean hasInstAwareBpps = hasInstantiationAwareBeanPostProcessors();
	// 須要依賴檢查
	boolean needsDepCheck = (mbd.getDependencyCheck() != AbstractBeanDefinition.DEPENDENCY_CHECK_NONE);
	PropertyDescriptor[] filteredPds = null;
    // 從 beanPostProcessors 對象中提取 BeanPostProcessor 結果集,遍歷後處理器
    for (BeanPostProcessor bp : getBeanPostProcessors()) {
    	...
    }
	// 在前面也出現過,用來進行依賴檢查
    filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
    checkDependencies(beanName, mbd, filteredPds, pvs);
	// 將屬性應用到 bean 中,使用深拷貝,將子類的屬性一併拷貝
	applyPropertyValues(beanName, mbd, bw, pvs);
}
複製代碼

因爲代碼太長,感興趣的小夥伴定位到 註釋 4.11 位置查看吧

介紹一下處理流程:

  1. 調用 InstantiationAwareBeanPostProcessor 處理器的 postProcessAfterInstantiation 方法,判斷控制程序是否繼續進行屬性填充
  2. 根據注入類型(byName/byType),提取依賴的 bean,統一存入 PropertyValues
  3. 判斷是否須要進行 BeanPostProcessor 和 依賴檢查:
  • 若是有後處理器,將會應用 InstantiationAwareBeanPostProcessor 處理器的 postProcessProperties 方法,對屬性獲取完畢填充前,對屬性進行再次處理。
  • 使用 checkDependencies 方法來進行依賴檢查
  1. 將全部解析到的 PropertyValues 中的屬性填充至 BeanWrapper 中。

在這個方法中,根據不一樣的注入類型進行屬性填充,而後調用後處理器進行處理,最終將屬性應用到 bean 中。

這裏也不細說,繼續往下走,看下一個方法


初始化 bean

在配置文件中,在使用 <bean> 標籤時,使用到了 init-method 屬性,這個屬性的做用就是在這個地方使用的:bean 實例化前,調用 init-method 指定的方法來根據用戶業務進行相應的實例化。來看下入口方法 initializeBean

// 調用初始化方法,例如 init-method
exposedObject = initializeBean(beanName, exposedObject, mbd);

protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd) {
	// 註釋 4.12 securityManage 是啥,不肯定=-=
	if (System.getSecurityManager() != null) {
		AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
			invokeAwareMethods(beanName, bean);
			return null;
		}, getAccessControlContext());
	}
	else {
		// 若是沒有 securityManage,方法裏面校驗了 bean 的類型,須要引用 Aware 接口
		// 對特殊的 bean 處理:Aware/ BeanClassLoader / BeanFactoryAware
		invokeAwareMethods(beanName, bean);
	}
	Object wrappedBean = bean;
	if (mbd == null || !mbd.isSynthetic()) {
		// 熟悉麼,後處理器又來了
		wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
	}
	// 激活用戶自定義的 init-method 方法
	invokeInitMethods(beanName, wrappedBean, mbd);
	if (mbd == null || !mbd.isSynthetic()) {
		wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
	}
	return wrappedBean;
}
複製代碼

這個方法主要是用來進行咱們設定的初始化方法的調用,不過在方法內部,還作了其它操做,因此一塊兒來說下流程:

一、激活 Aware 方法

Spring 中提供了一些 Aware 接口,實現了這個接口的 bean,在被初始化以後,能夠取得一些相對應的資源,例如 BeanFactoryAware,在初始化後, Spring 容器將會注入 BeanFactory 的實例。因此若是須要獲取這些資源,請引用 Aware 接口。

二、執行後處理器

相信這個你們已經不陌生了,咱們能夠在諸如 PostProcessor 等後處理器裏面自定義,實現修改和擴展。例如 BeanPostProcessor 類中有 postProcessBeforeInitializationpostProcessAfterInitialization,能夠對 bean 加載先後進行邏輯擴展,能夠將它理解成切面 AOP 的思想。

三、激活自定義的 init 方法

這個方法用途很明顯,就是找到用戶自定義的構造函數,而後調用它。要注意的是,若是 beanInitializingBean 類型話,須要調用 afterPropertiesSet 方法。

執行順序是先 afterPropertiesSet,接着纔是 init-method 定義的方法。


註冊 disposableBean

這是 Spring 提供銷燬方法的擴展入口,Spring 爸爸將咱們能考慮和想擴展的口子都給預留好。除了經過 destroy-method 屬性配置銷燬方法外,還能夠註冊後處理器 DestructionAwareBeanPostProcessor 來統一處理 bean 的銷燬方法:

protected void registerDisposableBeanIfNecessary(String beanName, Object bean, RootBeanDefinition mbd) {
	AccessControlContext acc = (System.getSecurityManager() != null ? getAccessControlContext() : null);
	if (!mbd.isPrototype() && requiresDestruction(bean, mbd)) {
		if (mbd.isSingleton()) {
			// 單例模式
			// 註冊 DisposableBean
			registerDisposableBean(beanName,
					new DisposableBeanAdapter(bean, beanName, mbd, getBeanPostProcessors(), acc));
		}
		else {
			// A bean with a custom scope...
			Scope scope = this.scopes.get(mbd.getScope());
			scope.registerDestructionCallback(beanName,
					new DisposableBeanAdapter(bean, beanName, mbd, getBeanPostProcessors(), acc));
		}
	}
}
複製代碼

這裏就是往不一樣的 scope 下, 進行 disposableBean 的註冊。


總結

本篇筆記總結了類加載的過程,結合時序圖和代碼分析,但願對它能有一個更深的瞭解。

同時對代碼編寫也有一點感觸:

  1. 不要寫過長的方法,儘可能拆分紅小方法,清晰意圖

從一開始看 Spring 源碼的時候,就驚歎於它代碼的整潔和邏輯清晰,入口方法展現須要作的事情,而後工做具體邏輯細分,體現了代碼設計者的高超設計。

因此在看到有幾個方法超過 100 行,心中小小吐槽了一下,看來我跟大佬們寫的代碼也有共同點,那就是還能夠進行優化哈哈哈~

  1. 要在關鍵地方都打上日誌,方便排查和定位

我截取的代碼片斷,因爲篇幅緣由,有些邏輯判斷和日誌處理都給摘掉了,可是日誌管理是很重要的一環,在關鍵地方打印日誌,在以後排查問題和分析數據都會有幫助。

若是懶得打印日誌,在關鍵的地方沒有打印日誌,即使出現了問題,也不知道從何查起,致使問題的緣由遲遲沒法暴露,形成用戶的投訴,那就得不償失了。

因爲我的技術有限,若是有理解不到位或者錯誤的地方,請留下評論,我會根據朋友們的建議進行修正

spring-analysis-note 碼雲 Gitee 地址

spring-analysis-note Github 地址


參考資料

  1. Spring Core Container 源碼分析三:Spring Beans 初始化流程分析

  2. Spring 源碼深度解析 / 郝佳編著. -- 北京 : 人民郵電出版社


傳送門:

相關文章
相關標籤/搜索