Spring IoC 依賴注入(四)構造器或工廠注入
Spring 核心編程思想目錄:https://www.cnblogs.com/binarylei/p/12290153.html
特別聲明: Bean 完整創建過程,包括實例化、依賴注入、初始化階段 。本文中 Bean 實例化指的是 Bean 對象的創建的第一階段 - 實例化,不包括之后的屬性注入、初始化。為了避免混淆,在此做一個約定,創建階段指完整的對象創建過程,實例化階段則單指第一階段。Spring 構造器注入或工廠注入也是在實例化過程中完成的,至於實例化之后的依賴注入指的是其它的所有注入方式,如 Settter 注入,字段注入等。
Bean 實例化大致可以分為以下場景:
- 構造器:包括無參構造器和有參構造器。
- 工廠方法:包括無參工廠方法和有參工廠方法。也包括靜態工廠和實例工廠。
本文重點分析以下方法:
-
beanFactory#doCreateBean:Bean 完整的創建過程,包括 Bean 實例化、依賴注入、初始化全部操作。
-
beanFactory#createBeanInstance:Bean 實例化過程。包括構造器、工廠方法,無參和有參,實例工廠和靜態工廠這些創建方式。
-
InstantiationStrategy:Spring 實例化策略接口提供了三個方法,分別用於實例化無參構造器,有參構造器,工廠方法。默認實現是 CglibSubclassingInstantiationStrategy。
-
beanFactory#instantiateBean:無參實例化 instantiateBean 方法,本質是直接獲取對象的默認構造器,通過反射創建對象,也就是調用 instantiationStrategy.instantiate 方法。
-
beanFactory#autowireConstructor:傳統方式,構造器實例化。委托給 ConstructorResolver#autowireConstructor。
-
beanFactory#instantiateUsingFactoryMethod:一般表示注解驅動( @Bean 注冊),工廠方法實例化。委托給 ConstructorResolver#instantiateUsingFactoryMethod。
-
ConstructorResolver#autowireConstructor:構造器實例化,主要解析構造器匹配和參數解析兩個問題。外部化參數覆蓋配置參數,且只能精確匹配,不推薦使用。默認配置參數允許參數個數大於配置的參數個數,不中的參數從 Spring IoC 容器中中查找。
-
ConstructorResolver 參數解析:ConstructorResolver 提供了以下方法進行參數解析:
- resolveConstructorArguments:解析默認的配置參數 mbd.constructorArgumentValues。
- createArgumentArray:除了已有的默認參數外,還會從容器中解析(僅當 autowiring=true),並將解析的結果包裝成 ArgumentsHolder。
- resolvePreparedArguments:解析已部分解析的參數 mbd.preparedArguments。
1. doCreateBean
Bean 完整的創建過程,包括 Bean 實例化、屬性注入、初始化全部操作。本文重點關注實例過程 createBeanInstance。
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd,
final @Nullable Object[] args) throws BeanCreationException {
BeanWrapper instanceWrapper = null;
// 1. 緩存的提前初始化的FactoryBean,為什么會提前初始化。
if (mbd.isSingleton()) {
instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
}
// 2. 實例化Bean,構造器注入或工廠方法注入
if (instanceWrapper == null) {
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
// 3. 屬性注入、初始化 ...
Object exposedObject = bean;
populateBean(beanName, mbd, instanceWrapper);
exposedObject = initializeBean(beanName, exposedObject, mbd);
...
}
說明: 我們重點說一下為什么會在緩存中獲取到提前部分初始化的 FactoryBean?
很明顯這些 factoryBean 實例還需要進行屬性注入、初始化等操作。我們知道,Spring 在提取對象類型時,FactoryBean 需要調用其 getObjectType 方法,此時就需要初始化 factoryBean 實例,但 Spring 為了減少提前初始化 Bean 產生的問題,只會將 factoryBean 提前實例化后緩存到 factoryBeanInstanceCache,當真正需要使用到 factoryBean 才進行完整的創建。事實上,只要我們進行依賴注入,調用 beanFactory#isTypeMatch 匹配對象類型,就會遍歷所有注冊的 beanDefinitionNames,從而實例化所有的 factoryBean 到 factoryBeanInstanceCache 中。
這就是 factoryBeanInstanceCache 中 factoryBean 實例的由來,具體見方法見 beanFactory#getSingletonFactoryBeanForTypeCheck,最終其實也是調用 beanFactory#createBeanInstance 方法。
2. createBeanInstance
createBeanInstance 方法用於 Bean 對象的實例化,包括工廠方法創建和構造器創建兩種。
- 校驗工作。解析 bd.beanClassName 為 bd.beanClass。當然如果是注解驅動,則不會設置 bd.beanClass 屬性,直接忽略。如果設置了 bd.beanClass,則需要校驗該類是否允許訪問。
- Supplier 創建對象:Spring 新推出的 bean 創建方式。
- 工廠創建:實際上,大部分情況都是注解驅動,因為 @Bean 本質是通過工廠方法創建的對象。全部委托給 instantiateUsingFactoryMethod 方法。
- 構造器創建:先判斷能否使用緩存快速實例化對象,如果有外部化參數 args,則不能使用緩存,因為外部化參數會覆蓋配置參數 bd.args,就點對工廠方法同樣有效。之后根據是否有參數或構造器判斷使用有參還是無參構造器實例對象。
protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {
// 1.1 確保此時beanClassName已經加載,當然注解驅動時不會設置beanClassName屬性
Class<?> beanClass = resolveBeanClass(mbd, beanName);
// 1.2 校驗beanClass允許訪問
if (beanClass != null && !Modifier.isPublic(beanClass.getModifiers()) && !mbd.isNonPublicAccessAllowed()) {
throw new BeanCreationException();
}
// 2. Supplier創建對象
Supplier<?> instanceSupplier = mbd.getInstanceSupplier();
if (instanceSupplier != null) {
return obtainFromSupplier(instanceSupplier, beanName);
}
// ========= 工廠方法實例化 =========
// 3. 工廠方法實例化,包括實例化工廠和靜態工廠
if (mbd.getFactoryMethodName() != null) {
return instantiateUsingFactoryMethod(beanName, mbd, args);
}
// ========= 構造器實例化 =========
// 4. 快速實例化對象,所謂的快速實例化,實際上是說使用緩存
boolean resolved = false;
boolean autowireNecessary = false;
// 4.1 args: 外部化參數,只能當無外部參數時才使用緩存。不推薦使用外部化參數
if (args == null) {
synchronized (mbd.constructorArgumentLock) {
// 4.2 是否使用緩存,其中autowireNecessary表示是否使用有參構造器
// 無參時肯定不會解析,為false。有參時會解析,為true
if (mbd.resolvedConstructorOrFactoryMethod != null) {
resolved = true;
autowireNecessary = mbd.constructorArgumentsResolved;
}
}
}
// 4.3 使用緩存,其中autowireNecessary表示是否使用有參構造器
if (resolved) {
if (autowireNecessary) {
// 4.4 有參構造器實例化
return autowireConstructor(beanName, mbd, null, null);
} else {
// 4.5 無參構造器實例化
return instantiateBean(beanName, mbd);
}
}
// 5. 到此,只能老老實實的解析,當然解析后會將解析后的構造器或參數緩存起來
// 5.1 是否指定了構造器,ibp#determineCandidateConstructors
Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
// 5.2 構造器實例化
if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR ||
mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {
return autowireConstructor(beanName, mbd, ctors, args);
}
// 5.3 不用管,默認都是 null。
ctors = mbd.getPreferredConstructors();
if (ctors != null) {
return autowireConstructor(beanName, mbd, ctors, null);
}
// 5.4 無參構造器實例化
return instantiateBean(beanName, mbd);
}
說明: createBeanInstance 方法在 bd.beanClass 校驗后進行對象實例化,而 Supplier 實例化比較簡單,工廠方法實例化又全部委托給了 instantiateUsingFactoryMethod 方法,基本上主要的功能就是,判斷如何使用構造器實例化對象。
- 判斷能否使用緩存快速實例化對象。使用緩存實例化有兩個條件:
- 外部化參數為 null。Spring 中獲取對象時,允許使用外部化參數 args 覆蓋配置參數 bd.args。此時,緩存的構造器和參數全部失效。雖然 Spring 提供了外部化參數 args,但不推薦使用 getBean(beanName, args) 。
- 緩存命中,也就是構造器和參數已經解析完成。如果無參,則不會解析參數,此時 bd.constructorArgumentsResolved=false,而有參則為 true。也就是可以使用該變量來判斷是否使用有參構造器。
- 判斷使用有參還是無參構造器實例化對象。使用有參構造器實例化對象有以下條件:
- 指定構造器。ibp#determineCandidateConstructors 后置處理器可以指定實例化的構造器 ctors。AutowiredAnnotationBeanPostProcessor 會指定實例化的構造器。
- 指定注入模式為構造器自動注入模式。目前這種模式只能通過 XML 方式配置。
<bean id="beanA" class="com.binarylei.spring.ioc.BeanA" autowire="constructor"/>
。 - 指定參數。包括配置參數 bd.constructorArgumentValues 或外部參數 args。
- 有參實例調用 autowireConstructor 方法。而無參實例化調用 instantiateBean 方法。無參實例化 instantiateBean 方法比較簡單,我們先看一下無參實例化。
2. 實例化策略 - InstantiationStrategy
Spring 在對象實例化上進行了抽象,提供了 InstantiationStrategy 接口用於實例對象,兩個實現類 SimpleInstantiationStrategy 和 CglibSubclassingInstantiationStrategy。
Spring 默認的實例化策略是 CglibSubclassingInstantiationStrategy,該類是為了解決 <replaced-method> 或 <lookup-method> 標簽,在這種情況下,必須進行字節碼提升才能實例化對象。通常我們不會用到這些標簽,本文只會介紹常規的實例化方法,直接調用反射實例化對象,即其父類 SimpleInstantiationStrategy。
InstantiationStrategy 提供了三個方法,分別用於實例化無參構造器,有參構造器,工廠方法。
// 1. 無參構造器實例化
Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner);
throws BeansException;
// 2. 有參構造器實例化
Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner,
Constructor<?> ctor, Object... args) throws BeansException;
// 3. 工廠方法實例化
Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner,
Object factoryBean, Method factoryMethod, Object... args) throws BeansException;
說明: SimpleInstantiationStrategy 實現都非常簡單,無非是通過 Java 反射實例化對象。
注意:無參構造器是會緩存 bd.resolvedConstructorOrFactoryMethod。
3. instantiateBean
無參實例化 instantiateBean 方法,本質是直接獲取對象的默認構造器,通過反射創建對象,也就是調用 instantiationStrategy.instantiate 方法。
protected BeanWrapper instantiateBean(final String beanName, final RootBeanDefinition mbd) {
// 1. 調用無參構造器實例化對象
Object beanInstance = getInstantiationStrategy().instantiate(mbd, beanName, this);
BeanWrapper bw = new BeanWrapperImpl(beanInstance);
// 2. 配置bw的類型轉換器,依賴注入時可能需要對注入的屬性值進行類型轉換
initBeanWrapper(bw);
return bw;
}
4. autowireConstructor
autowireConstructor 和 instantiateUsingFactoryMethod 方法其實沒有本質的區別,都是通過參數注入。autowireConstructor 通過構造器實例化,instantiateUsingFactoryMethod 通過特定的工廠方法實例化,如果將構造方法當作一種特殊的方法,那么這兩種注入方式沒有本質的區別。事實上,Spring 也是將這兩種注入方式統一進行處理的。
-
autowireConstructor:傳統方式,通過構造器實例化。委托給 ConstructorResolver#autowireConstructor。
-
instantiateUsingFactoryMethod:一般表示注解驅動( @Bean 注冊),通過工廠方法實例化。委托給 ConstructorResolver#instantiateUsingFactoryMethod。
4.1 整體說明
本文重點分析構造器注入 autowireConstructor 方法,了解構造器注入后,工廠注入基本上如出一轍。無論是構造器注入還是工廠方法注入都會面臨如下問題:
- 外部化參數 explicitArgs 覆蓋配置參數 bd.args。如果調用 beanFactory#getBean(beanName, args) 時,實例化對象時,只會使用外部參數。同時外部化參數只支持精確匹配,即方法參數個數和外部化參數個數必須完全一致。但我們還是推薦使用配置參數,而不推薦使用外部化參數。這一思想貫穿了整個參數匹配的過程,閱讀源碼代碼時要首先要有這么一個印象。
- (緩存)快速實例化對象。只要執行過一次對象創建,bd 會緩存匹配的構造器和參數。注意,如果使用外部化參數,那么不會使用緩存,因為外部化參數發生變化后,匹配的構造器和參數會改變。還有一點,如果是單例,會直接從緩存中命中對象,根本不會再次調用 autowireConstructor 方法創建對象,此時外部化參數不會生效,這可能會讓使用者非常困惑,為什么外部化參數就是不生效呢?
- mbd.constructorArgumentLock:同步鎖
- mbd.resolvedConstructorOrFactoryMethod:緩存構造器或工廠方法。
- mbd.constructorArgumentsResolved:參數是否解析,boolean 值。
- mbd.resolvedConstructorArguments:緩存解析后的參數。
- mbd.preparedConstructorArguments:緩存參數,使用進還需要進一步解析。
- (非緩存)匹配無參構造器。外部化參數和配置參數匹配規則相同:外部化參數和配置參數都為 null,且只有一個無參的構造器。
- (非緩存)匹配有參構造器。先計算實際可用的參數,進行參數匹配。如果匹配成功,則計算每個構造器的權重,權重越小優先級越高。如果無法匹配或有多個權重相同的構造器,則拋出異常。最后緩存匹配成功的構造器和參數。外部化參數和默認配置參數匹配規則的最大不同是:
- 外部化參數匹配:實際參數全部來源於外部化參數。也是就是說,必須精確匹配外部化參數個數,匹配規則非常簡單。
- 默認配置參數匹配:實際參數除了來源於默認的配置參數外,不足的參數還可以根據其參數類型和參數名稱從 Spring IoC 容器中查找(僅當autowiring=true)。也是就是說,構造參數的個數可以大於實際參數個數。當然,如果 Spring IoC 容器中查找不到對應的參數,則該構造器方法肯定無法匹配。
public BeanWrapper autowireConstructor(String beanName, RootBeanDefinition mbd,
@Nullable Constructor<?>[] chosenCtors, @Nullable Object[] explicitArgs) {
// 1. 使用緩存,快速實例化對象
Constructor<?> constructorToUse = (Constructor<?>) mbd.resolvedConstructorOrFactoryMethod;
ArgumentsHolder argsToUse = mbd.resolvedConstructorArguments;
// 非緩存,進行參數匹配
if (constructorToUse == null || argsToUse == null) {
// 2. (非緩存)匹配無參構造器
if (candidates.length == 1 && explicitArgs == null && !mbd.hasConstructorArgumentValues()) {
return ...
}
// 3. (非緩存)匹配有參構造器
for (Constructor<?> candidate : candidates) {
}
}
return bw;
}
說明: 構造器實例化對象,代碼比較長。我會將其拆開,大致分為緩存匹配、無參構造器匹配,有參構造器匹配三步分。
4.2 緩存匹配
只要對象實例化后,bd 就會緩存其構造方法或參數等信息用於快速實例化對象。緩存這一塊比較簡單,重要的弄清楚這些緩存變量的含義,以及什么時候緩存的?
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) {
argsToResolve = mbd.preparedConstructorArguments;
}
}
}
if (argsToResolve != null) {
argsToUse = resolvePreparedArguments(beanName, mbd, bw, constructorToUse, argsToResolve, true);
}
}
說明: 可以很清楚的看到外部化參數沒有使用緩存,每次都需要重新解析。至於參數的解析 resolvePreparedArguments 會在下方進行分析。
4.3 無參構造器匹配
無參構造器匹配也很簡單,如果沒有指定構造器,則對所有的構造器進行匹配。當只有一個無參構造器,且無論是外部化參數或配置參數都為空時,則使用無參構造器。
Constructor<?>[] candidates = chosenCtors;
if (candidates == null) {
Class<?> beanClass = mbd.getBeanClass();
candidates = (mbd.isNonPublicAccessAllowed() ? beanClass.getDeclaredConstructors() : beanClass.getConstructors());
}
if (candidates.length == 1 && explicitArgs == null && !mbd.hasConstructorArgumentValues()) {
Constructor<?> uniqueCandidate = candidates[0];
if (uniqueCandidate.getParameterCount() == 0) {
synchronized (mbd.constructorArgumentLock) {
mbd.resolvedConstructorOrFactoryMethod = uniqueCandidate;
mbd.constructorArgumentsResolved = true;
mbd.resolvedConstructorArguments = EMPTY_ARGS;
}
bw.setBeanInstance(instantiate(beanName, mbd, uniqueCandidate, EMPTY_ARGS));
return bw;
}
}
說明: 無參構造器解析后,會緩存所有的變量,不會再次解析。最復雜的是有參構造器的解析。
4.4 有參構造器匹配
有參構造器匹配比較復雜,但也是有跡可循的:
- 首先,實際參數解析。對於外部化參數而言,只能使用指定的參數。而對於默認配置參數而言,除了配置參數外,還可以根據根據其參數類型和參數名稱從 Spring IoC 容器中查找(僅當autowiring=true)。也就是說外部化參數必須精確匹配外部化參數個數,而默認配置參數則構造參數的個數可以大於實際參數個數,只要可以從容器中解析到參數即可。
- 其次,權值計算。權重越小優先級越高。如果無法匹配或有多個權重相同的構造器,則拋出異常。
- 最后,緩存解析的構造器和參數,返回實例化的對象。
(1)實際參數解析
對於外部化參數就不多說了,我們重點說一下絕大多數場景,默認配置參數。首先,解析已有的配置參數;其次,根據構造器參數類型和參數名稱從 Spring IoC 容器中解析剩余的參數。如果能成功解析,則命中該構造器。
// autowiring表示是否允許從 Spring IoC 容器查找依賴
boolean autowiring = (chosenCtors != null ||
mbd.getResolvedAutowireMode() == AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR);
ConstructorArgumentValues resolvedValues = null;
int minNrOfArgs;
// 1.1 外部化參數,不需要解析
if (explicitArgs != null) {
minNrOfArgs = explicitArgs.length;
// 1.2 默認配置參數,先解析已有的配置參數
} else {
ConstructorArgumentValues cargs = mbd.getConstructorArgumentValues();
resolvedValues = new ConstructorArgumentValues();
minNrOfArgs = resolveConstructorArguments(beanName, mbd, bw, cargs, resolvedValues);
}
AutowireUtils.sortConstructors(candidates);
int minTypeDiffWeight = Integer.MAX_VALUE;
Set<Constructor<?>> ambiguousConstructors = null;
LinkedList<UnsatisfiedDependencyException> causes = null;
for (Constructor<?> candidate : candidates) {
int parameterCount = candidate.getParameterCount();
if (constructorToUse != null && argsToUse != null && argsToUse.length > parameterCount) {
break;
}
if (parameterCount < minNrOfArgs) {
continue;
}
ArgumentsHolder argsHolder;
Class<?>[] paramTypes = candidate.getParameterTypes();
// 1.3 默認配置參數,如果配置參數不足,從Spring IoC中繼續解析參數。如果能解析則匹配成功
if (resolvedValues != null) {
try {
String[] paramNames = ConstructorPropertiesChecker.evaluate(candidate, parameterCount);
if (paramNames == null) {
ParameterNameDiscoverer pnd = this.beanFactory.getParameterNameDiscoverer();
if (pnd != null) {
paramNames = pnd.getParameterNames(candidate);
}
}
argsHolder = createArgumentArray(beanName, mbd, resolvedValues, bw, paramTypes, paramNames, getUserDeclaredConstructor(candidate), autowiring, candidates.length == 1);
} catch (UnsatisfiedDependencyException ex) {
causes.add(ex);
continue;
}
// 1.4 外部化參數,只需要參數個數相等即可。此時resolvedValues=null
} else {
if (parameterCount != explicitArgs.length) {
continue;
}
argsHolder = new ArgumentsHolder(explicitArgs);
}
...
}
說明: 根據實際的參數解析匹配可用的構造器。
- 外部化參數:精確匹配參數個數。
- 默認配置參數匹配:允許構造器參數大於默認配置參數個數,只要不足的參數能從 Spring IoC容器中解析即可。其實最關鍵的就是兩個參數解析函數:
- resolveConstructorArguments:解析已經有默認參數 bd.constructorArgumentValues。
- createArgumentArray:除了已有的默認參數外,還會從容器中解析(僅當autowiring=true),並將解析的結果包裝成 ArgumentsHolder。
- 我們重點分析一下 autowiring=true 是什么場景,也就是允許從 Spring IoC 容器中查找依賴自動注入?
- 構造器自動注入模式:即 bd.autowireMode=AUTOWIRE_CONSTRUCTOR。我們知道,除了 XML 顯示配置構造器自動注入外,一般情況下 bd.autowireMode=NO,也就是返回 false。
- 顯示的指定構造器:即 chosenCtors!=null,還記得我們之前分析的 createBeanInstance 方法嗎?通過后置處理器 ibp#determineCandidateConstructors 可以指定實例化的構造器 ctors。如果裝載AutowiredAnnotationBeanPostProcessor 后,會使用構造器自動注入。
(2)權值計算
權重越小優先級越高。如果無法匹配或有多個權重相同的構造器,則拋出異常。constructorToUse 匹配
int typeDiffWeight = (mbd.isLenientConstructorResolution() ?
argsHolder.getTypeDifferenceWeight(paramTypes) : argsHolder.getAssignabilityWeight(paramTypes));
// 權重最高的構造器
if (typeDiffWeight < minTypeDiffWeight) {
constructorToUse = candidate;
argsHolderToUse = argsHolder;
argsToUse = argsHolder.arguments;
minTypeDiffWeight = typeDiffWeight;
ambiguousConstructors = null;
// 多個權重最高的構造器
} else if (constructorToUse != null && typeDiffWeight == minTypeDiffWeight) {
if (ambiguousConstructors == null) {
ambiguousConstructors = new LinkedHashSet<>();
ambiguousConstructors.add(constructorToUse);
}
ambiguousConstructors.add(candidate);
}
說明: constructorToUse 表示權重最高的構造器,ambiguousConstructors 表示有多個權重最高的構造器。如果沒有匹配的構造器(constructorToUse=null)或有多個權重相等的構造器(ambiguousConstructors!=null)都需要拋出異常。下面我們看一下權重的計算,以 getAssignabilityWeight 為例,權重值越小優先級越高。
// paramTypes 表示構造器或方法參數類型列表
public int getAssignabilityWeight(Class<?>[] paramTypes) {
// 1. arguments表示解析后的參數
for (int i = 0; i < paramTypes.length; i++) {
if (!ClassUtils.isAssignableValue(paramTypes[i], this.arguments[i])) {
return Integer.MAX_VALUE;
}
}
// 2. arguments表示解析前的參數,如配置參數
for (int i = 0; i < paramTypes.length; i++) {
if (!ClassUtils.isAssignableValue(paramTypes[i], this.rawArguments[i])) {
return Integer.MAX_VALUE - 512;
}
}
return Integer.MAX_VALUE - 1024;
}
(3)實例化對象
// 1. 緩存解析后的構造器和參數
if (explicitArgs == null && argsHolderToUse != null) {
argsHolderToUse.storeCache(mbd, constructorToUse);
}
// 2. 調用InstantiationStrategy實例化參數
bw.setBeanInstance(instantiate(beanName, mbd, constructorToUse, argsToUse));
return bw;
說明: 在構造器實例化對象開始前,第一步就是從 bd 緩存中快速實例化對象。其中無參構造器已經在匹配無參構造器時緩存,此時需要緩存有參構造器匹配的變量。
public void storeCache(RootBeanDefinition mbd, Executable constructorOrFactoryMethod) {
synchronized (mbd.constructorArgumentLock) {
mbd.resolvedConstructorOrFactoryMethod = constructorOrFactoryMethod;
mbd.constructorArgumentsResolved = true;
if (this.resolveNecessary) {
mbd.preparedConstructorArguments = this.preparedArguments;
} else {
mbd.resolvedConstructorArguments = this.arguments;
}
}
}
在 ArgumentsHolder 對象中有三個變量,都表示參數:
-
arguments:已經解析后的參數,不需要再進行解析,直接可以注入。
-
preparedArguments:已部分解析后的參數,這個主要是用來解析注入的實際參數。
-
rawArguments:原始的參數,這個主要是用來計算構造器的權重。
到目前為止,ConstructorResolver#autowireConstructor 構造器實例化已經分析完畢,autowireConstructor 方法最主要的兩個功能就是解析參數,匹配構造器。構造器的匹配我們已經分析過了,接下為是遺留下來的參數解析。
5. ConstructorResolver 參數解析
ConstructorResolver 提供了以下方法進行參數解析:
- resolveConstructorArguments:解析默認的配置參數 mbd.constructorArgumentValues。
- createArgumentArray:除了已有的默認參數外,還會從容器中解析(僅當 autowiring=true),並將解析的結果包裝成 ArgumentsHolder。
- resolvePreparedArguments:解析已部分解析的參數 mbd.preparedArguments。
5.1 resolveConstructorArguments
resolveConstructorArguments 方法解析默認的配置參數 mbd.constructorArgumentValues。
private int resolveConstructorArguments(String beanName, RootBeanDefinition mbd, BeanWrapper bw, ConstructorArgumentValues cargs, ConstructorArgumentValues resolvedValues) {
TypeConverter customConverter = this.beanFactory.getCustomTypeConverter();
TypeConverter converter = (customConverter != null ? customConverter : bw);
BeanDefinitionValueResolver valueResolver =
new BeanDefinitionValueResolver(this.beanFactory, beanName, mbd, converter);
int minNrOfArgs = cargs.getArgumentCount();
for (Map.Entry<Integer, ConstructorArgumentValues.ValueHolder> entry : cargs.getIndexedArgumentValues().entrySet()) {
ConstructorArgumentValues.ValueHolder valueHolder = entry.getValue();
if (valueHolder.isConverted()) {
resolvedValues.addIndexedArgumentValue(index, valueHolder);
} else {
// 核心代碼就這么一句,將參數解析全部委托給valueResolver
Object resolvedValue =
valueResolver.resolveValueIfNecessary("constructor argument", valueHolder.getValue());
ConstructorArgumentValues.ValueHolder resolvedValueHolder =
new ConstructorArgumentValues.ValueHolder(resolvedValue, valueHolder.getType(), valueHolder.getName());
resolvedValueHolder.setSource(valueHolder);
resolvedValues.addIndexedArgumentValue(index, resolvedValueHolder);
}
}
// 泛型參數解析 ...
return minNrOfArgs;
}
說明: resolveConstructorArguments 方法的核心代碼就一句,valueResolver.resolveValueIfNecessary。看到 BeanDefinitionValueResolver 你是否會感到很熟悉呢?在 setter 等注入時,調用 beanFactory#applyPropertyValues 方法也需要解析 mbd.pvs 參數,同樣也會用到 valueResolver.resolveValueIfNecessary 來解析配置參數,不管是那種注入方式,參數的解析都是一樣的。
5.2 createArgumentArray
createArgumentArray 方法很長,其實邏輯反而並不復雜,遍歷構造器的所有參數,先到 resolveConstructorArguments 方法已經解析過的參數中查找參數,如果查找到就使用配置的參數。當然如果查找不到,這時就判斷是否允許自行注入,如果允許,則從 Spring IoC 容器中查找依賴,即調用 beanFactory#resolveDependency 方法。
/**
* @param resolvedValues 表示bd.constructorArgumentValues中已經解析的參數
* @param paramTypes 表示當前的構造器或方法的參數類型列表
* @param paramNames 方法的參數名稱
* @param executable 方法或構造器
* @param autowiring 是否采用構造器自動注入,如果是則會從Spring IoC容器中查找參數
* @param fallback 如果已經有多個構造器或方法匹配,則fallback=false
*/
private ArgumentsHolder createArgumentArray(
String beanName, RootBeanDefinition mbd, @Nullable ConstructorArgumentValues resolvedValues,
BeanWrapper bw, Class<?>[] paramTypes, @Nullable String[] paramNames, Executable executable,
boolean autowiring, boolean fallback) throws UnsatisfiedDependencyException {
TypeConverter customConverter = this.beanFactory.getCustomTypeConverter();
TypeConverter converter = (customConverter != null ? customConverter : bw);
ArgumentsHolder args = new ArgumentsHolder(paramTypes.length);
Set<ConstructorArgumentValues.ValueHolder> usedValueHolders = new HashSet<>(paramTypes.length);
Set<String> autowiredBeanNames = new LinkedHashSet<>(4);
for (int paramIndex = 0; paramIndex < paramTypes.length; paramIndex++) {
Class<?> paramType = paramTypes[paramIndex];
String paramName = (paramNames != null ? paramNames[paramIndex] : "");
// 1. 從默認的配置參數中查找參數
ConstructorArgumentValues.ValueHolder valueHolder = null;
if (resolvedValues != null) {
// 1.1 查找已經有參數,可以根據參數的索引或名稱查找
valueHolder = resolvedValues.getArgumentValue(paramIndex, paramType, paramName, usedValueHolders);
// 1.2 如果不是自動注入,則要想方設法查找參數,因為自動注入還可以從容器中查找參數
if (valueHolder == null && (!autowiring || paramTypes.length == resolvedValues.getArgumentCount())) {
valueHolder = resolvedValues.getGenericArgumentValue(null, null, usedValueHolders);
}
}
// 2. 已經從配置該參數,這部分代碼省略
if (valueHolder != null) {
...
// 3. 沒有配置該參數,嘗試從Spring IoC容器中查找
} else {
MethodParameter methodParam = MethodParameter.forExecutable(executable, paramIndex);
if (!autowiring) {
throw new UnsatisfiedDependencyException();
}
// 3.1 核心代碼,調用 beanFactory#resolveDependency
Object autowiredArgument = resolveAutowiredArgument(
methodParam, beanName, autowiredBeanNames, converter, fallback);
args.rawArguments[paramIndex] = autowiredArgument;
args.arguments[paramIndex] = autowiredArgument;
// 3.2 自動注入標記位
args.preparedArguments[paramIndex] = autowiredArgumentMarker;
args.resolveNecessary = true;
}
}
for (String autowiredBeanName : autowiredBeanNames) {
this.beanFactory.registerDependentBean(autowiredBeanName, beanName);
}
return args;
}
說明: 對於構造器中的參數,如果在配置參數中命中,使用配置的參數。對於沒有命中的參數,如果允許自動注入,則調用 beanFactory#resolveDependency 從容器中查找。
5.3 resolvePreparedArguments
resolvePreparedArguments 方法是對 mbd.preparedConstructorArguments 中還需要解析的參數調用 BeanDefinitionValueResolver#resolveValueIfNecessary 進行解析。
private Object[] resolvePreparedArguments(String beanName, RootBeanDefinition mbd, BeanWrapper bw, Executable executable, Object[] argsToResolve, boolean fallback) {
TypeConverter customConverter = this.beanFactory.getCustomTypeConverter();
TypeConverter converter = (customConverter != null ? customConverter : bw);
BeanDefinitionValueResolver valueResolver =
new BeanDefinitionValueResolver(this.beanFactory, beanName, mbd, converter);
Class<?>[] paramTypes = executable.getParameterTypes();
Object[] resolvedArgs = new Object[argsToResolve.length];
for (int argIndex = 0; argIndex < argsToResolve.length; argIndex++) {
Object argValue = argsToResolve[argIndex];
MethodParameter methodParam = MethodParameter.forExecutable(executable, argIndex);
// createArgumentArray 方法設置的自動注入標記
if (argValue == autowiredArgumentMarker) {
argValue = resolveAutowiredArgument(methodParam, beanName, null, converter, fallback);
} else if (argValue instanceof BeanMetadataElement) {
argValue = valueResolver.resolveValueIfNecessary("constructor argument", argValue);
} else if (argValue instanceof String) {
argValue = this.beanFactory.evaluateBeanDefinitionString((String) argValue, mbd);
}
Class<?> paramType = paramTypes[argIndex];
resolvedArgs[argIndex] = converter.convertIfNecessary(argValue, paramType, methodParam);
}
return resolvedArgs;
}
說明: 核心就是 BeanDefinitionValueResolver#resolveValueIfNecessary,其實所以的類型注入最終都逃不離 beanFactory#resolveDependency 這個最基礎的方法,而這個方法的核心是 Spring 的類型自省。
6. instantiateUsingFactoryMethod
其實也了解了構造器實例化 autowireConstructor 之后,instantiateUsingFactoryMethod 方法基本大同小異。同樣是匹配工廠方法,解析參數。
每天用心記錄一點點。內容也許不重要,但習慣很重要!