Spring的IOC中屬性注入的過程

背景

上一篇從源碼解讀Spring的IOC講解了spring容器的初始化以及依賴注入的過程,但是在其中有一個很重要的部分暫時還沒講。可能已經有人發現了,那篇文章雖然講解了IOC容器創建對象的整個流程,但是好像並沒有涉及我們的依賴對象以及屬性是怎麼注入到對象當中的,所以這篇文章就來專門分析屬性注入的整個過程

分析

首先,考慮到很多人並沒有看過之前的文章,所以我並不準備接着那篇文章講,這裏採用其他方法來進行分析

這裏我們有一個對象:

@Component
public class TestClass {

    public TestClass() {
        System.out.println("-----無參構造調用-----");
    }
}

然後下面是測試類:

@RunWith(SpringRunner.class)
@SpringBootTest
public class TestClassTest {

    @Autowired
    private TestClass testClass;

    @Test
    public void test() {}
}

執行這個測試方法,我們會發現在控制檯中輸出了那句話,說明我們注入變量成功了,接下來我們就在這裏打上斷點,來分析spring爲什麼會自動執行這個方法:
斷點
調用棧
我們來分析這個調用棧,我們斷點的位置是<init>,瞭解JVM類加載機制的應該對這個方法很眼熟,這個方法就是我們的構造方法

我們往前看,緊接着這個方法執行前,先執行了newInstance方法,這是一個反射方法,原來spring最終是通過反射來創建我們的實例對象

然後我們再來找調用反射方法的位置,這裏看到了一個BeanUtils對象,我們進入這個對象,找到instantiateClass方法:

	public static <T> T instantiateClass(Constructor<T> ctor, Object... args) throws BeanInstantiationException {
		Assert.notNull(ctor, "Constructor must not be null");
		try {
			ReflectionUtils.makeAccessible(ctor);
			return (KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isKotlinType(ctor.getDeclaringClass()) ?
					KotlinDelegate.instantiateClass(ctor, args) : ctor.newInstance(args));
		}
		catch (InstantiationException ex) {
			throw new BeanInstantiationException(ctor, "Is it an abstract class?", ex);
		}
		catch (IllegalAccessException ex) {
			throw new BeanInstantiationException(ctor, "Is the constructor accessible?", ex);
		}
		catch (IllegalArgumentException ex) {
			throw new BeanInstantiationException(ctor, "Illegal arguments for constructor", ex);
		}
		catch (InvocationTargetException ex) {
			throw new BeanInstantiationException(ctor, "Constructor threw exception", ex.getTargetException());
		}
	}

除去異常捕獲和非空校驗,核心代碼實際就兩行:

		// 使構造函數可顯式訪問
		ReflectionUtils.makeAccessible(ctor);
		// 
		return (KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isKotlinType(ctor.getDeclaringClass()) ?
					KotlinDelegate.instantiateClass(ctor, args) : ctor.newInstance(args));

第一行顯式設置構造函數是可訪問的,同時只有在必需的情況下才調用setAccessible(true)方法,以避免和JVM的安全管理髮生衝突

接下來的代碼中,ctor指向了構造方法,我們看這個三目運算符的條件語句:

  • KotlinDetector.isKotlinReflectPresent():表示是否存在Kotlin反射
  • KotlinDetector.isKotlinType(ctor.getDeclaringClass()):判斷是否是Kotlin類型

不懂Kotlin是什麼的不要緊,只需要知道這裏判斷注入的類是否是Kotlin類型,來選擇使用Kotlin注入還是Java反射注入

我們接着看
SimpleInstantiationStrategy
剛纔的instantiateClass方法是在SimpleInstantiationStrategy類中的instantiate方法中被調用的,我們進入這個方法:

	@Override
	public Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) {
		// 如果沒有覆寫(重載)的方法,則不使用cglib來覆寫類
		if (!bd.hasMethodOverrides()) {
			Constructor<?> constructorToUse;
			synchronized (bd.constructorArgumentLock) {
				// 獲取構造方法
				constructorToUse = (Constructor<?>) bd.resolvedConstructorOrFactoryMethod;
				if (constructorToUse == null) {
					final Class<?> clazz = bd.getBeanClass();
					if (clazz.isInterface()) {
						throw new BeanInstantiationException(clazz, "Specified class is an interface");
					}
					try {
						if (System.getSecurityManager() != null) {
							constructorToUse = AccessController.doPrivileged(
									(PrivilegedExceptionAction<Constructor<?>>) clazz::getDeclaredConstructor);
						}
						else {
							constructorToUse = clazz.getDeclaredConstructor();
						}
						bd.resolvedConstructorOrFactoryMethod = constructorToUse;
					}
					catch (Throwable ex) {
						throw new BeanInstantiationException(clazz, "No default constructor found", ex);
					}
				}
			}
			// 使用bean工具類實例化類
			return BeanUtils.instantiateClass(constructorToUse);
		}
		else {
			// 使用cglib實例化類
			return instantiateWithMethodInjection(bd, beanName, owner);
		}
	}

看似方法很長,實際大部分都代碼段在正常情況下都不會執行到,整段代碼的流程配合註釋應該很容易看懂,這個方法的作用就是選擇實例化bean的方式,同時爲確保能夠正常實例化類,對構造函數進行了預檢

接着我們看其之前的方法調用棧
這個類就很熟悉了,是之前分析依賴注入過程中的核心類,整段代碼這裏不貼出了,只看該方法中的一段核心代碼:

		beanInstance = getInstantiationStrategy().instantiate(mbd, beanName, parent);

getInstantiationStrategy()從字面意思上可以看出是用來獲取策略類,我們回過去看SimpleInstantiationStrategy方法,果然是一個策略類(不懂策略類的可以去了解一下設計模式中的策略模式),這裏調用設置的策略類的策略方法(實例化對象的方法),最終生成被注入的對象實例,這裏的三個參數分別是以下含義:

  • mbd:RootBeanDefinition類型,指向被注入的對象類型,同時包含有一些諸如其scope類型,是否是抽象類等屬性
  • beanName:對象的名稱(注意,不是類名,而是bean id名)
  • parent:實例化bean的工廠類(在方法中有parent = this語句)

再往上就是從源碼解讀Spring的IOC我的這篇文章中的內容了,整個屬性注入的過程就結束了

結束了嗎?

先別急,我們剛纔的斷點僅僅是打在構造函數處,我們也僅僅是明白了構造函數在依賴注入中的執行過程和執行時機,我們來把TestClass稍微改動一下:

@Component
public class TestClass {

    @Autowired
    private Demo demo;

    public TestClass() {
        System.out.println("-----無參構造調用-----");
    }
}

這個Demo對象中僅有一個無參的構造函數,我們執行測試方法,發現先調用了Demo的構造函數,再調用了TestClass的構造函數

我們在Demo對象的構造函數裏打上斷點,進行調試,但是我們並不是要像剛纔那樣倒推執行流程,因爲TestClass構造函數的執行在其之後,所以我們直接跳到下一個斷點:TestClass的構造函數處,但是跳過去之後,我們發現一個很奇怪的現象,明明執行了Demo的構造函數,爲什麼還是不注入依賴?
demo
別急,我們已經知道,構造方法是在AbstractAutowireCapableBeanFactory對象的doCreateBean方法中的createBeanInstance方法執行的,我們直接跳過來執行,我們重點是紅框中的語句:
bean
這裏我們發現了,執行完圖中紅框的語句後,我們已經獲取了TestClass的bean,但是其中的對象依然爲空:
屬性爲空
我們採用單步跳過的方式,發現在執行了下面這條語句後,demo對象被注入了

		populateBean(beanName, mbd, instanceWrapper);

我們進入該方法,其代碼如下:

	/**
	 * @param beanName 被注入屬性的bean的id
	 * @param mbd 被注入屬性的bean
	 * @param bw 被注入屬性的bean進行了一次封裝後的包裝類
	 */
	protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {
		if (bw == null) {
			if (mbd.hasPropertyValues()) {
				throw new BeanCreationException(
						mbd.getResourceDescription(), beanName, "Cannot apply property values to null instance");
			}
			else {
				// 如果實例爲null,就不注入
				return;
			}
		}
		
		boolean continueWithPropertyPopulation = true;
		
		// 給後置處理器一次在同步前修改bean狀態的機會
		if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
			for (BeanPostProcessor bp : getBeanPostProcessors()) {
				if (bp instanceof InstantiationAwareBeanPostProcessor) {
					InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
					// postProcessAfterInstantiation是在實例化之後,注入屬性之前執行的方法,默認實現均返回true
					if (!ibp.postProcessAfterInstantiation(bw.getWrappedInstance(), beanName)) {
						continueWithPropertyPopulation = false;
						break;
					}
				}
			}
		}

		if (!continueWithPropertyPopulation) {
			return;
		}

		// 判斷是否定義了屬性值
		PropertyValues pvs = (mbd.hasPropertyValues() ? mbd.getPropertyValues() : null);
		// 如果autowire方式是通過名稱或類型自動裝配,就先進行裝配
		// 裝配的結果並不直接添加到bean中,而是先添加到pvs中
		if (mbd.getResolvedAutowireMode() == AUTOWIRE_BY_NAME || mbd.getResolvedAutowireMode() == AUTOWIRE_BY_TYPE) {
			MutablePropertyValues newPvs = new MutablePropertyValues(pvs);
			
			if (mbd.getResolvedAutowireMode() == AUTOWIRE_BY_NAME) {
				autowireByName(beanName, mbd, bw, newPvs);
			}
			
			if (mbd.getResolvedAutowireMode() == AUTOWIRE_BY_TYPE) {
				autowireByType(beanName, mbd, bw, newPvs);
			}
			pvs = newPvs;
		}

		// 判斷該bean工廠中是否有應用於單例bean關閉的後置處理器
		boolean hasInstAwareBpps = hasInstantiationAwareBeanPostProcessors();
		// 判斷是否有依賴(繼承基類)
		boolean needsDepCheck = (mbd.getDependencyCheck() != AbstractBeanDefinition.DEPENDENCY_CHECK_NONE);

		PropertyDescriptor[] filteredPds = null;
		if (hasInstAwareBpps) {
			if (pvs == null) {
				// 即使沒有值,也會創建一個空數組
				pvs = mbd.getPropertyValues();
			}
			for (BeanPostProcessor bp : getBeanPostProcessors()) {
				if (bp instanceof InstantiationAwareBeanPostProcessor) {
					InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
					// 在交付bean之前進行後置處理,不需要屬性描述符
					// 返回應用於給定bean的實際屬性值
					PropertyValues pvsToUse = ibp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);
					if (pvsToUse == null) {
						if (filteredPds == null) {
							filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
						}
						// 對給定屬性值進行後置處理,同時會檢查是否滿足依賴,比如基於@Required註解的檢查
						pvsToUse = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);
						if (pvsToUse == null) {
							return;
						}
					}
					pvs = pvsToUse;
				}
			}
		}
		if (needsDepCheck) {
			if (filteredPds == null) {
				// 獲取bean屬性的描述符
				filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
			}
			checkDependencies(beanName, mbd, filteredPds, pvs);
		}

		if (pvs != null) {
			// 解析bean的運行時引用,使用深層複製
			applyPropertyValues(beanName, mbd, bw, pvs);
		}
	}

這個方法的代碼看似很長,實際上核心只有一個,就是postProcessProperties方法,而我們的屬性注入也正是由這個方法調用的,到這裏,整個屬性注入的過程才真正結束

總結

正篇博客基本是想到哪寫到哪,可能比較混亂,所以在最後重新給大家捋一捋這個單純的屬性注入的過程,這裏均以AbstractAutowireCapableBeanFactory的doCreateBean()方法作爲起點:

構造方法的執行位置
  1. 以createBeanInstance作爲起點,執行bean的初始化
  2. 在這個方法內部,使用策略模式,選擇一個策略類執行初始化操作
  3. 通過反射獲取構造方法,選擇工具類準備執行
  4. 在工具類中設置構造函數的可見性
  5. 根據bean的類型,判斷使用Kotlin實例化,還是Java反射實例化
屬性注入的執行位置
  1. 以populateBean作爲起點,執行屬性注入
  2. 遍歷bean的所有後置處理器,找到InstantiationAwareBeanPostProcessor類型的處理器(在bean初始化之後執行的處理器)
  3. 調用postProcessProperties進行屬性注入
  4. 如果有運行時引用,調用applyPropertyValues使用深層複製

好了,這樣整個IOC的流程就講完了,整段過程錯綜複雜,出現錯誤也在所難免,歡迎各位進行指正,共同進步

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章