Spring IOC 容器源碼分析 - 創建原始 bean 對象
1. 簡介
本篇文章是上一篇文章(創建單例 bean 的過程)的延續。在上一篇文章中,我們從戰略層面上領略了doCreateBean
方法的全過程。本篇文章,我們就從戰術的層面上,詳細分析doCreateBean
方法中的一個重要的調用,即createBeanInstance
方法。在本篇文章中,你將看到三種不同的構造 bean 對象的方式。你也會了解到構造 bean 對象的兩種策略。如果你對這些內容感興趣,那麽不妨繼續往下讀。我會在代碼進行大量的註解,相信能幫助你理解代碼邏輯。好了,其他的就不多說了,進入正題吧。
2. 源碼分析
2.1 創建 bean 對象的過程
本節,我們一起來來分析一下本篇文章的主角createBeanInstance
protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, Object[] args) {
Class<?> beanClass = resolveBeanClass(mbd, beanName);
/*
* 檢測類的訪問權限。默認情況下,對於非 public 的類,是允許訪問的。
* 若禁止訪問,這裏會拋出異常
*/
if (beanClass != null && !Modifier.isPublic(beanClass.getModifiers()) && !mbd.isNonPublicAccessAllowed()) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Bean class isn‘t public, and non-public access not allowed: " + beanClass.getName ());
}
/*
* 如果工廠方法不為空,則通過工廠方法構建 bean 對象。這種構建 bean 的方式
* 就不深入分析了,有興趣的朋友可以自己去看一下。
*/
if (mbd.getFactoryMethodName() != null) {
// 通過“工廠方法”的方式構建 bean 對象
return instantiateUsingFactoryMethod(beanName, mbd, args);
}
/*
* 當多次構建同一個 bean 時,可以使用此處的快捷路徑,即無需再次推斷應該使用哪種方式構造實例,
* 以提高效率。比如在多次構建同一個 prototype 類型的 bean 時,就可以走此處的捷徑。
* 這裏的 resolved 和 mbd.constructorArgumentsResolved 將會在 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) {
// 通過“構造方法自動註入”的方式構造 bean 對象
return autowireConstructor(beanName, mbd, null, null);
}
else {
// 通過“默認構造方法”的方式構造 bean 對象
return instantiateBean(beanName, mbd);
}
}
// 由後置處理器決定返回哪些構造方法,這裏不深入分析了
Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
/*
* 下面的條件分支條件用於判斷使用什麽方式構造 bean 實例,有兩種方式可選 - 構造方法自動
* 註入和默認構造方法。判斷的條件由4部分綜合而成,如下:
*
* 條件1:ctors != null -> 後置處理器返回構造方法數組是否為空
*
* 條件2:mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_CONSTRUCTOR
* -> bean 配置中的 autowire 屬性是否為 constructor
* 條件3:mbd.hasConstructorArgumentValues()
* -> constructorArgumentValues 是否存在元素,即 bean 配置文件中
* 是否配置了 <construct-arg/>
* 條件4:!ObjectUtils.isEmpty(args)
* -> args 數組是否存在元素,args 是由用戶調用
* getBean(String name, Object... args) 傳入的
*
* 上面4個條件,只要有一個為 true,就會通過構造方法自動註入的方式構造 bean 實例
*/
if (ctors != null ||
mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_CONSTRUCTOR ||
mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {
// 通過“構造方法自動註入”的方式構造 bean 對象
return autowireConstructor(beanName, mbd, ctors, args);
}
// 通過“默認構造方法”的方式構造 bean 對象
return instantiateBean(beanName, mbd);
}
以上就是 createBeanInstance 方法的源碼,不是很長。配合著註釋,應該不是很難懂。下面我們來總結一下這個方法的執行流程,如下:
- 檢測類的訪問權限,若禁止訪問,則拋出異常
- 若工廠方法不為空,則通過工廠方法構建 bean 對象,並返回結果
- 若構造方式已解析過,則走快捷路徑構建 bean 對象,並返回結果
- 如第三步不滿足,則通過組合條件決定使用哪種方式構建 bean 對象
這裏有三種構造 bean 對象的方式,如下:
- 通過“工廠方法”的方式構造 bean 對象
- 通過“構造方法自動註入”的方式構造 bean 對象
- 通過“默認構造方法”的方式構造 bean 對象
下面我將會分析第2和第3種構造 bean 對象方式的實現源碼。至於第1種方式,實現邏輯和第2種方式較為相似。所以就不分析了,大家有興趣可以自己看一下。
2.2 通過構造方法自動註入的方式創建 bean 實例
本節,我將會分析構造方法自動註入的實現邏輯。代碼邏輯較為復雜,需要大家耐心閱讀。代碼如下:
protected BeanWrapper autowireConstructor(
String beanName, RootBeanDefinition mbd, Constructor<?>[] ctors, Object[] explicitArgs) {
// 創建 ConstructorResolver 對象,並調用其 autowireConstructor 方法
return new ConstructorResolver(this).autowireConstructor(beanName, mbd, ctors, explicitArgs);
}
public BeanWrapper autowireConstructor(final String beanName, final RootBeanDefinition mbd,
Constructor<?>[] chosenCtors, final Object[] explicitArgs) {
// 創建 BeanWrapperImpl 對象
BeanWrapperImpl bw = new BeanWrapperImpl();
this.beanFactory.initBeanWrapper(bw);
Constructor<?> constructorToUse = null;
ArgumentsHolder argsHolderToUse = null;
Object[] argsToUse = null;
// 確定參數值列表(argsToUse)
if (explicitArgs != null) {
argsToUse = explicitArgs;
}
else {
Object[] argsToResolve = null;
synchronized (mbd.constructorArgumentLock) {
// 獲取已解析的構造方法
constructorToUse = (Constructor<?>) mbd.resolvedConstructorOrFactoryMethod;
if (constructorToUse != null && mbd.constructorArgumentsResolved) {
// 獲取已解析的構造方法參數列表
argsToUse = mbd.resolvedConstructorArguments;
if (argsToUse == null) {
// 若 argsToUse 為空,則獲取未解析的構造方法參數列表
argsToResolve = mbd.preparedConstructorArguments;
}
}
}
if (argsToResolve != null) {
// 解析參數列表
argsToUse = resolvePreparedArguments(beanName, mbd, bw, constructorToUse, argsToResolve);
}
}
if (constructorToUse == null) {
boolean autowiring = (chosenCtors != null ||
mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_CONSTRUCTOR);
ConstructorArgumentValues resolvedValues = null;
int minNrOfArgs;
if (explicitArgs != null) {
minNrOfArgs = explicitArgs.length;
}
else {
ConstructorArgumentValues cargs = mbd.getConstructorArgumentValues();
resolvedValues = new ConstructorArgumentValues();
/*
* 確定構造方法參數數量,比如下面的配置:
* <bean id="persion" class="xyz.coolblog.autowire.Person">
* <constructor-arg index="0" value="xiaoming"/>
* <constructor-arg index="1" value="1"/>
* <constructor-arg index="2" value="man"/>
* </bean>
*
* 此時 minNrOfArgs = maxIndex + 1 = 2 + 1 = 3,除了計算 minNrOfArgs,
* 下面的方法還會將 cargs 中的參數數據轉存到 resolvedValues 中
*/
minNrOfArgs = resolveConstructorArguments(beanName, mbd, bw, cargs, resolvedValues);
}
// 獲取構造方法列表
Constructor<?>[] candidates = chosenCtors;
if (candidates == null) {
Class<?> beanClass = mbd.getBeanClass();
try {
candidates = (mbd.isNonPublicAccessAllowed() ?
beanClass.getDeclaredConstructors() : beanClass.getConstructors());
}
catch (Throwable ex) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Resolution of declared constructors on bean Class [" + beanClass.getName() +
"] from ClassLoader [" + beanClass.getClassLoader() + "] failed", ex);
}
}
// 按照構造方法的訪問權限級別和參數數量進行排序
AutowireUtils.sortConstructors(candidates);
int minTypeDiffWeight = Integer.MAX_VALUE;
Set<Constructor<?>> ambiguousConstructors = null;
LinkedList<UnsatisfiedDependencyException> causes = null;
for (Constructor<?> candidate : candidates) {
Class<?>[] paramTypes = candidate.getParameterTypes();
/*
* 下面的 if 分支的用途是:若匹配到到合適的構造方法了,提前結束 for 循環
* constructorToUse != null 這個條件比較好理解,下面分析一下條件 argsToUse.length > paramTypes.length:
* 前面說到 AutowireUtils.sortConstructors(candidates) 用於對構造方法進行
* 排序,排序規則如下:
* 1. 具有 public 訪問權限的構造方法排在非 public 構造方法前
* 2. 參數數量多的構造方法排在前面
*
* 假設現在有一組構造方法按照上面的排序規則進行排序,排序結果如下(省略參數名稱):
*
* 1. public Hello(Object, Object, Object)
* 2. public Hello(Object, Object)
* 3. public Hello(Object)
* 4. protected Hello(Integer, Object, Object, Object)
* 5. protected Hello(Integer, Object, Object)
* 6. protected Hello(Integer, Object)
*
* argsToUse = [num1, obj2],可以匹配上的構造方法2和構造方法6。由於構造方法2有
* 更高的訪問權限,所以沒理由不選他(盡管後者在參數類型上更加匹配)。由於構造方法3
* 參數數量 < argsToUse.length,參數數量上不匹配,也不應該選。所以
* argsToUse.length > paramTypes.length 這個條件用途是:在條件
* constructorToUse != null 成立的情況下,通過判斷參數數量與參數值數量
* (argsToUse.length)是否一致,來決定是否提前終止構造方法匹配邏輯。
*/
if (constructorToUse != null && argsToUse.length > paramTypes.length) {
break;
}
/*
* 構造方法參數數量低於配置的參數數量,則忽略當前構造方法,並重試。比如
* argsToUse = [obj1, obj2, obj3, obj4],上面的構造方法列表中,
* 構造方法1、2和3顯然不是合適選擇,忽略之。
*/
if (paramTypes.length < minNrOfArgs) {
continue;
}
ArgumentsHolder argsHolder;
if (resolvedValues != null) {
try {
/*
* 判斷否則方法是否有 ConstructorProperties 註解,若有,則取註解中的
* 值。比如下面的代碼:
*
* public class Persion {
* private String name;
* private Integer age;
*
* @ConstructorProperties(value = {"coolblog", "20"})
* public Persion(String name, Integer age) {
* this.name = name;
* this.age = age;
* }
* }
*/
String[] paramNames = ConstructorPropertiesChecker.evaluate(candidate, paramTypes.length);
if (paramNames == null) {
ParameterNameDiscoverer pnd = this.beanFactory.getParameterNameDiscoverer();
if (pnd != null) {
/*
* 獲取構造方法參數名稱列表,比如有這樣一個構造方法:
* public Person(String name, int age, String sex)
*
* 調用 getParameterNames 方法返回 paramNames = [name, age, sex]
*/
paramNames = pnd.getParameterNames(candidate);
}
}
/*
* 創建參數值列表,返回 argsHolder 會包含進行類型轉換後的參數值,比如下
* 面的配置:
*
* <bean id="persion" class="xyz.coolblog.autowire.Person">
* <constructor-arg name="name" value="xiaoming"/>
* <constructor-arg name="age" value="1"/>
* <constructor-arg name="sex" value="man"/>
* </bean>
*
* Person 的成員變量 age 是 Integer 類型的,但由於在 Spring 配置中
* 只能配成 String 類型,所以這裏要進行類型轉換。
*/
argsHolder = createArgumentArray(beanName, mbd, resolvedValues, bw, paramTypes, paramNames,
getUserDeclaredConstructor(candidate), autowiring);
}
catch (UnsatisfiedDependencyException ex) {
if (this.beanFactory.logger.isTraceEnabled()) {
this.beanFactory.logger.trace(
"Ignoring constructor [" + candidate + "] of bean ‘" + beanName + "‘: " + ex);
}
if (causes == null) {
causes = new LinkedList<UnsatisfiedDependencyException>();
}
causes.add(ex);
continue;
}
}
else {
if (paramTypes.length != explicitArgs.length) {
continue;
}
argsHolder = new ArgumentsHolder(explicitArgs);
}
/*
* 計算參數值(argsHolder.arguments)每個參數類型與構造方法參數列表
* (paramTypes)中參數的類型差異量,差異量越大表明參數類型差異越大。參數類型差異
* 越大,表明當前構造方法並不是一個最合適的候選項。引入差異量(typeDiffWeight)
* 變量目的:是將候選構造方法的參數列表類型與參數值列表類型的差異進行量化,通過量化
* 後的數值篩選出最合適的構造方法。
*
* 講完差異量,再來說說 mbd.isLenientConstructorResolution() 條件。
* 官方的解釋是:返回構造方法的解析模式,有寬松模式(lenient mode)和嚴格模式
* (strict mode)兩種類型可選。具體的細節沒去研究,就不多說了。
*/
int typeDiffWeight = (mbd.isLenientConstructorResolution() ?
argsHolder.getTypeDifferenceWeight(paramTypes) : argsHolder.getAssignabilityWeight(paramTypes));
if (typeDiffWeight < minTypeDiffWeight) {
constructorToUse = candidate;
argsHolderToUse = argsHolder;
argsToUse = argsHolder.arguments;
minTypeDiffWeight = typeDiffWeight;
ambiguousConstructors = null;
}
/*
* 如果兩個構造方法與參數值類型列表之間的差異量一致,那麽這兩個方法都可以作為
* 候選項,這個時候就出現歧義了,這裏先把有歧義的構造方法放入
* ambiguousConstructors 集合中
*/
else if (constructorToUse != null && typeDiffWeight == minTypeDiffWeight) {
if (ambiguousConstructors == null) {
ambiguousConstructors = new LinkedHashSet<Constructor<?>>();
ambiguousConstructors.add(constructorToUse);
}
ambiguousConstructors.add(candidate);
}
}
// 若上面未能篩選出合適的構造方法,這裏將拋出 BeanCreationException 異常
if (constructorToUse == null) {
if (causes != null) {
UnsatisfiedDependencyException ex = causes.removeLast();
for (Exception cause : causes) {
this.beanFactory.onSuppressedException(cause);
}
throw ex;
}
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Could not resolve matching constructor " +
"(hint: specify index/type/name arguments for simple parameters to avoid type ambiguities)");
}
/*
* 如果 constructorToUse != null,且 ambiguousConstructors 也不為空,表明解析
* 出了多個的合適的構造方法,此時就出現歧義了。Spring 不會擅自決定使用哪個構造方法,
* 所以拋出異常。
*/
else if (ambiguousConstructors != null && !mbd.isLenientConstructorResolution()) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Ambiguous constructor matches found in bean ‘" + beanName + "‘ " +
"(hint: specify index/type/name arguments for simple parameters to avoid type ambiguities): " +
ambiguousConstructors);
}
if (explicitArgs == null) {
/*
* 緩存相關信息,比如:
* 1. 已解析出的構造方法對象 resolvedConstructorOrFactoryMethod
* 2. 構造方法參數列表是否已解析標誌 constructorArgumentsResolved
* 3. 參數值列表 resolvedConstructorArguments 或 preparedConstructorArguments
*
* 這些信息可用在其他地方,用於進行快捷判斷
*/
argsHolderToUse.storeCache(mbd, constructorToUse);
}
}
try {
Object beanInstance;
if (System.getSecurityManager() != null) {
final Constructor<?> ctorToUse = constructorToUse;
final Object[] argumentsToUse = argsToUse;
beanInstance = AccessController.doPrivileged(new PrivilegedAction<Object>() {
@Override
public Object run() {
return beanFactory.getInstantiationStrategy().instantiate(
mbd, beanName, beanFactory, ctorToUse, argumentsToUse);
}
}, beanFactory.getAccessControlContext());
}
else {
/*
* 調用實例化策略創建實例,默認情況下使用反射創建實例。如果 bean 的配置信息中
* 包含 lookup-method 和 replace-method,則通過 CGLIB 增強 bean 實例
*/
beanInstance = this.beanFactory.getInstantiationStrategy().instantiate(
mbd, beanName, this.beanFactory, constructorToUse, argsToUse);
}
// 設置 beanInstance 到 BeanWrapperImpl 對象中
bw.setBeanInstance(beanInstance);
return bw;
}
catch (Throwable ex) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Bean instantiation via constructor failed", ex);
}
}
上面的方法邏輯比較復雜,做了不少事情,該方法的核心邏輯是根據參數值類型篩選合適的構造方法。解析出合適的構造方法後,剩下的工作就是構建 bean 對象了,這個工作交給了實例化策略去做。下面羅列一下這個方法的工作流程吧:
- 創建 BeanWrapperImpl 對象
- 解析構造方法參數,並算出 minNrOfArgs
- 獲取構造方法列表,並排序
- 遍歷排序好的構造方法列表,篩選合適的構造方法
- 獲取構造方法參數列表中每個參數的名稱
- 再次解析參數,此次解析會將
- 計算構造方法參數列表與參數值列表之間的類型差異量,以篩選出更為合適的構造方法
- 緩存已篩選出的構造方法以及參數值列表,若再次創建 bean 實例時,可直接使用,無需再次進行篩選
- 使用初始化策略創建 bean 對象
- 將 bean 對象放入 BeanWrapperImpl 對象中,並返回該對象
由上面的流程可以看得出,通過構造方法自動註入的方式構造 bean 對象的過程還是很復雜的。為了看懂這個流程,我進行了多次調試,算是勉強弄懂大致邏輯。由於時間有限,我並未能詳細分析 autowireConstructor 方法及其所調用的一些方法,比如 resolveConstructorArguments、 autowireConstructor 等。關於這些方法,這裏只寫了個大概,有興趣的朋友自己去探索吧。
2.3 通過默認構造方法創建 bean 對象
看完了上面冗長的邏輯,本節來看點輕松的吧 - 通過默認構造方法創建 bean 對象。如下:
protected BeanWrapper instantiateBean(final String beanName, final RootBeanDefinition mbd) {
try {
Object beanInstance;
final BeanFactory parent = this;
// if 條件分支裏的一大坨是 Java 安全相關的代碼,可以忽略,直接看 else 分支
if (System.getSecurityManager() != null) {
beanInstance = AccessController.doPrivileged(new PrivilegedAction<Object>() {
@Override
public Object run() {
return getInstantiationStrategy().instantiate(mbd, beanName, parent);
}
}, getAccessControlContext());
}
else {
/*
* 調用實例化策略創建實例,默認情況下使用反射創建對象。如果 bean 的配置信息中
* 包含 lookup-method 和 replace-method,則通過 CGLIB 創建 bean 對象
*/
beanInstance = getInstantiationStrategy().instantiate(mbd, beanName, parent);
}
// 創建 BeanWrapperImpl 對象
BeanWrapper bw = new BeanWrapperImpl(beanInstance);
initBeanWrapper(bw);
return bw;
}
catch (Throwable ex) {
throw new BeanCreationException(
mbd.getResourceDescription(), beanName, "Instantiation of bean failed", ex);
}
}
public Object instantiate(RootBeanDefinition bd, String beanName, BeanFactory owner) {
// 檢測 bean 配置中是否配置了 lookup-method 或 replace-method,若配置了,則需使用 CGLIB 構建 bean 對象
if (bd.getMethodOverrides().isEmpty()) {
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(new PrivilegedExceptionAction<Constructor<?>>() {
@Override
public Constructor<?> run() throws Exception {
return clazz.getDeclaredConstructor((Class[]) null);
}
});
}
else {
// 獲取默認構造方法
constructorToUse = clazz.getDeclaredConstructor((Class[]) null);
}
// 設置 resolvedConstructorOrFactoryMethod
bd.resolvedConstructorOrFactoryMethod = constructorToUse;
}
catch (Throwable ex) {
throw new BeanInstantiationException(clazz, "No default constructor found", ex);
}
}
}
// 通過無參構造方法創建 bean 對象
return BeanUtils.instantiateClass(constructorToUse);
}
else {
// 使用 GCLIG 創建 bean 對象
return instantiateWithMethodInjection(bd, beanName, owner);
}
}
上面就是通過默認構造方法創建 bean 對象的過程,比較簡單,就不多說了。最後我們再來看看簡單看看通過無參構造方法剛創建 bean 對象的代碼(通過 CGLIB 創建 bean 對象的方式就不看了)是怎樣的,如下:
public static <T> T instantiateClass(Constructor<T> ctor, Object... args) throws BeanInstantiationException {
Assert.notNull(ctor, "Constructor must not be null");
try {
// 設置構造方法為可訪問
ReflectionUtils.makeAccessible(ctor);
// 通過反射創建 bean 實例,這裏的 args 是一個沒有元素的空數組
return 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());
}
}
到這裏,終於看到了創建 bean 對象的代碼了。在經歷層層調用後,我們總算是追到了調用棧的最深處。看到這裏,大家可以休息一下了,本文也差不多要結束了。好了,最後再容我多啰嗦一會,往下看。
3.寫在最後
寫到這裏,我也算是松了一口氣,終於快寫完了。這篇文章寫起來感覺挺不容易的,原因是 createBeanInstance 及其調用的方法是在太多了,而且很多方法邏輯還是比較復雜的,尤其是 autowireConstructor 中調用的一些方法。autowireConstructor 中調用的方法我基本上都看了一遍,但並非全部都弄懂了,有些方法只是知道個大概。所以,這篇文章寫的我挺糾結的,生怕有些地方分析的不對。由於我後續還有很多東西要看,以至於我暫時沒法抽出大量的時間去詳細閱讀 Spring 的源碼。所以如果上面的分析有不對的地方,歡迎指正,我會虛心聽之。如果這些不對的地方給你造成了困擾,實在很抱歉,抱歉。
好了,本篇文章先到這裏。謝謝閱讀!
參考
- 《Spring 源碼深度解析》- 郝佳
本文在知識共享許可協議 4.0 下發布,轉載需在明顯位置處註明出處
作者:coolblog.xyz
本文同步發布在我的個人博客:http://www.coolblog.xyz
本作品采用知識共享署名-非商業性使用-禁止演繹 4.0 國際許可協議進行許可。
Spring IOC 容器源碼分析 - 創建原始 bean 對象