前言
在前面的講解中,我們瞭解瞭如何獲取構造器。當只有一個符合條件的構造器時,自然會選擇它作為初始化的構造器。然而,在上一節中,我們遇到了一種特殊情況:當有多個符合條件的構造器時,返回的是一個陣列。在這種情況下,Spring又是如何從多個構造器中選擇最合適的呢?今天,我們將討論的主題是:autowireConstructor方法。
autowireConstructor
讓我們首先深入研究一下該方法的主要原始碼,畢竟原始碼是最好的老師。
public BeanWrapper autowireConstructor(String beanName, RootBeanDefinition mbd,
@Nullable Constructor<?>[] chosenCtors, @Nullable Object[] explicitArgs) {
BeanWrapperImpl bw = new BeanWrapperImpl();
this.beanFactory.initBeanWrapper(bw);
Constructor<?> constructorToUse = null;
ArgumentsHolder argsHolderToUse = null;
Object[] argsToUse = null;
// 如果getBean()傳入了args,那構造方法要用的入參就直接確定好了
if (explicitArgs != null) {
argsToUse = explicitArgs;
}
else {
Object[] argsToResolve = null;
synchronized (mbd.constructorArgumentLock) {
constructorToUse = (Constructor<?>) mbd.resolvedConstructorOrFactoryMethod;
if (constructorToUse != null && mbd.constructorArgumentsResolved) {
// Found a cached constructor...
argsToUse = mbd.resolvedConstructorArguments;
if (argsToUse == null) {
argsToResolve = mbd.preparedConstructorArguments;
}
}
}
if (argsToResolve != null) {
argsToUse = resolvePreparedArguments(beanName, mbd, bw, constructorToUse, argsToResolve);
}
}
// 如果沒有確定要使用的構造方法,或者確定了構造方法但是所要傳入的引數值沒有確定
if (constructorToUse == null || argsToUse == null) {
// Take specified constructors, if any.
// 如果沒有指定構造方法,那就獲取beanClass中的所有構造方法所謂候選者
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);
}
}
// 如果只有一個候選構造方法,並且沒有指定所要使用的構造方法引數值,並且該構造方法是無參的,那就直接用這個無參構造方法進行例項化了
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;
}
}
// Need to resolve the constructor.
boolean autowiring = (chosenCtors != null ||
mbd.getResolvedAutowireMode() == AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR);
ConstructorArgumentValues resolvedValues = null;
// 確定要選擇的構造方法的引數個數的最小值,後續判斷候選構造方法的引數個數如果小於minNrOfArgs,則直接pass掉
int minNrOfArgs;
if (explicitArgs != null) {
// 如果直接傳了構造方法引數值,那麼所用的構造方法的引數個數肯定不能少於
minNrOfArgs = explicitArgs.length;
}
else {
// 如果透過BeanDefinition傳了構造方法引數值,因為有可能是透過下標指定了,比如0位置的值,2位置的值,雖然只指定了2個值,但是構造方法的引數個數至少得是3個
ConstructorArgumentValues cargs = mbd.getConstructorArgumentValues();
resolvedValues = new ConstructorArgumentValues();
// 處理RuntimeBeanReference
minNrOfArgs = resolveConstructorArguments(beanName, mbd, bw, cargs, resolvedValues);
}
// 對候選構造方法進行排序,public的方法排在最前面,都是public的情況下引數個數越多越靠前
AutowireUtils.sortConstructors(candidates);
int minTypeDiffWeight = Integer.MAX_VALUE;
Set<Constructor<?>> ambiguousConstructors = null;
Deque<UnsatisfiedDependencyException> causes = null;
// 遍歷每個構造方法,進行篩選
for (Constructor<?> candidate : candidates) {
// 引數個數
int parameterCount = candidate.getParameterCount();
// 本次遍歷時,之前已經選出來了所要用的構造方法和入參物件,並且入參物件個數比當前遍歷到的這個構造方法的引數個數多,則不用再遍歷,退出迴圈
if (constructorToUse != null && argsToUse != null && argsToUse.length > parameterCount) {
// Already found greedy constructor that can be satisfied ->
// do not look any further, there are only less greedy constructors left.
break;
}
// 如果引數個數小於所要求的引數個數,則遍歷下一個,這裡考慮的是同時存在public和非public的構造方法
if (parameterCount < minNrOfArgs) {
continue;
}
ArgumentsHolder argsHolder;
Class<?>[] paramTypes = candidate.getParameterTypes();
// 沒有透過getBean()指定構造方法引數值
if (resolvedValues != null) {
try {
// 如果在構造方法上使用了@ConstructorProperties,那麼就直接取定義的值作為構造方法的引數名
String[] paramNames = ConstructorPropertiesChecker.evaluate(candidate, parameterCount);
// 獲取構造方法引數名
if (paramNames == null) {
ParameterNameDiscoverer pnd = this.beanFactory.getParameterNameDiscoverer();
if (pnd != null) {
paramNames = pnd.getParameterNames(candidate);
}
}
// 根據引數型別、引數名找到對應的bean物件
argsHolder = createArgumentArray(beanName, mbd, resolvedValues, bw, paramTypes, paramNames,
getUserDeclaredConstructor(candidate), autowiring, candidates.length == 1);
}
catch (UnsatisfiedDependencyException ex) {
// 當前正在遍歷的構造方法找不到可用的入參物件,記錄一下
if (logger.isTraceEnabled()) {
logger.trace("Ignoring constructor [" + candidate + "] of bean '" + beanName + "': " + ex);
}
// Swallow and try next constructor.
if (causes == null) {
causes = new ArrayDeque<>(1);
}
causes.add(ex);
continue;
}
}
else {
// Explicit arguments given -> arguments length must match exactly.
// 在調getBean方法時傳入了引數值,那就表示只能用對應引數個數的構造方法
if (parameterCount != explicitArgs.length) {
continue;
}
// 不用再去BeanFactory中查詢bean物件了,已經有了,同時當前正在遍歷的構造方法就是可用的構造方法
argsHolder = new ArgumentsHolder(explicitArgs);
}
// 當前遍歷的構造方法所需要的入參物件都找到了,根據引數型別和找到的引數物件計算出來一個匹配值,值越小越匹配
// Lenient表示寬鬆模式
int typeDiffWeight = (mbd.isLenientConstructorResolution() ?
argsHolder.getTypeDifferenceWeight(paramTypes) : argsHolder.getAssignabilityWeight(paramTypes));
// Choose this constructor if it represents the closest match.
// 值越小越匹配
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);
}
}
// 遍歷結束 x
// 如果沒有可用的構造方法,就取記錄的最後一個異常並丟擲
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 on bean class [" + mbd.getBeanClassName() + "] " +
"(hint: specify index/type/name arguments for simple parameters to avoid type ambiguities)");
}
// 如果有可用的構造方法,但是有多個
else if (ambiguousConstructors != null && !mbd.isLenientConstructorResolution()) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Ambiguous constructor matches found on bean class [" + mbd.getBeanClassName() + "] " +
"(hint: specify index/type/name arguments for simple parameters to avoid type ambiguities): " +
ambiguousConstructors);
}
// 如果沒有透過getBean方法傳入引數,並且找到了構造方法以及要用的入參物件則快取
if (explicitArgs == null && argsHolderToUse != null) {
argsHolderToUse.storeCache(mbd, constructorToUse);
}
}
Assert.state(argsToUse != null, "Unresolved constructor arguments");
bw.setBeanInstance(instantiate(beanName, mbd, constructorToUse, argsToUse));
return bw;
}
在進入這個方法之前,還存在一個快取層,因為原型BeanDefinition可能會多次建立Bean,但無需每次都重新尋找構造器。因此,當第一次找到構造器時,會被快取起來。如果快取中已經存在構造方法,那麼可以直接進行例項化,無需再次執行推斷方法。如果在之前的步驟中沒有找到其他構造器,那麼將會使用無參構造器來例項化Bean。
推斷方法判斷
我們現在來仔細觀察一下autowireConstructor方法的整體流程,這樣我們可以更清楚地理解其運作方式。
如果沒有明確確定要使用的構造方法,或者已確定構造方法但其所需傳入引數值尚未確定。
- 當沒有確定要使用的構造方法時,可以遍歷類中的所有構造方法。
- 當類中只存在一個無參構造方法時,可以直接使用該無參構造方法進行例項化,無需額外的選擇操作。
- 在選擇構造方法時,需要確定所需引數個數的最小值。若已傳入構造方法引數值,則所選構造方法的引數個數必不少於傳入值的個數;若未傳入引數值,則需檢查BeanDefinition中是否指定了某個下標的值,確保最小值大於該下標。
比如這樣配置:
public class UserServiceBeanPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
BeanDefinition userService = beanFactory.getBeanDefinition("userService");
//這裡我瞎寫的null,正常應該是物件。
userService.getConstructorArgumentValues().addIndexedArgumentValue(1,null);
}
}
-
對候選構造方法進行排序。首先,將public修飾的構造方法排在最前面;若所有構造方法均為public,那麼引數個數越多的構造方法越靠前。
-
遍歷每個構造方法
-
在呼叫getBean()方法時,如果不指定構造方法的引數值,系統會根據構造器中的引數型別和引數名來匹配相應的bean物件。
-
在呼叫getBean()方法時,如果指定了構造方法的引數值,系統會直接利用這些引數值來例項化bean物件
-
在確定構造方法時,儘管找到了匹配的構造方法引數值,但並不意味著這個構造方法是最佳選擇。因此,需要考慮是否存在多個構造方法匹配了相同的值。在這種情況下,系統將會根據值和構造方法型別之間的匹配程度進行評分,以找到最佳匹配的構造方法。
分值越低越匹配
打分規則是基於分值越低越匹配的原則。要確定分值,我們需要將程式碼提取出來進行執行,因為底層的邏輯相當複雜,需要仔細分析。
public int getTypeDifferenceWeight(Class<?>[] paramTypes) {
// 最終值和型別的匹配程度
int typeDiffWeight = MethodInvoker.getTypeDifferenceWeight(paramTypes, this.arguments);
// 原始值和型別的匹配程度,並減掉1024,使得原始值的匹配值更優先,意思就是優先根據原始值來算匹配值
int rawTypeDiffWeight = MethodInvoker.getTypeDifferenceWeight(paramTypes, this.rawArguments) - 1024;
// 取最小值
return Math.min(rawTypeDiffWeight, typeDiffWeight);
}
那麼,讓我們來檢視一下getTypeDifferenceWeight方法能夠輸出怎樣的數值。
首先,我們定義一個A類,該類繼承自B類,而B類又繼承自C類,同時A類實現了介面D。
Object[] objects = new Object[]{new A()};
// 0
System.out.println(MethodInvoker.getTypeDifferenceWeight(new Class[]{A.class}, objects));
// 2
System.out.println(MethodInvoker.getTypeDifferenceWeight(new Class[]{B.class}, objects));
// 4
System.out.println(MethodInvoker.getTypeDifferenceWeight(new Class[]{C.class}, objects));
// 1
System.out.println(MethodInvoker.getTypeDifferenceWeight(new Class[]{D.class}, objects));
透過仔細觀察,我們可以明白為什麼分值越低越具有匹配性。
總結
在本文中,我們深入研究了Spring框架中的autowireConstructor方法。該方法用於在存在多個構造器時選擇最合適的構造器進行例項化Bean。透過分析原始碼和推斷方法判斷的流程,我們瞭解到系統是如何根據引數個數、型別和數值的匹配程度來選擇最佳構造器的。
在實際應用中,我們需要注意遍歷構造方法、引數個數的最小值、排序規則、引數值的匹配等細節。
總的來說,autowireConstructor方法是Spring框架中一個關鍵的方法,它為我們提供了靈活且智慧的構造器選擇機制,幫助我們更好地管理Bean的例項化過程。透過學習和掌握這一方法,我們能夠更好地運用Spring框架。