上一篇文章中,咱們說到了Spring肯定有哪些構造器他可使用,這一篇文章中,咱們未來分析Spring是如何找到一個最合適的構造器。web
@Service
public class DemoServiceOne { DemoServiceTwo demoServiceTwo; DemoServiceThree demoServiceThree; public DemoServiceOne(){ } @Autowired(required = false) public DemoServiceOne(DemoServiceTwo demoServiceTwo){ this.demoServiceTwo = demoServiceTwo; } @Autowired(required = false) public DemoServiceOne(DemoServiceThree demoServiceThree, DemoServiceTwo demoServiceTwo){ this.demoServiceTwo = demoServiceTwo; this.demoServiceThree = demoServiceThree; } } 複製代碼
以上面這個類爲例,Spring可使用的構造器,最終肯定爲三個,以下圖:spring
這裏簡單說明一下,圖片中構造器的肯定是上一篇文章分析的內容,若有疑問可查看:如何肯定構造器 這裏經過下圖簡單說明,這幾個構造器是如何添加到ctors
中的: app
接下來就是本文的重點,經過構造器來完成對象的實例化。再有多個構造器的時候,是如何選出最合適的一個呢?框架
public BeanWrapper autowireConstructor(String beanName, RootBeanDefinition mbd, @Nullable Constructor<?>[] chosenCtors, @Nullable Object[] explicitArgs) { // 實例化一個BeanWrapperImpl對象 BeanWrapperImpl bw = new BeanWrapperImpl(); this.beanFactory.initBeanWrapper(bw); /** * 肯定參數列表,第一種經過BeanDefinition 設置 * 也能夠經過 xml設置 * constructorToUse spring 決定採用哪一個構造方法初始化 */ Constructor<?> constructorToUse = null; ArgumentsHolder argsHolderToUse = null; // 定義構造方法要使用那些參數 Object[] argsToUse = null; 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, true); } } // 沒有已解析的構造方法 if (constructorToUse == null || argsToUse == null) { // 解析構造方法 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; /** * 定義了最小參數個數 * 若是給構造方法的參數列表給定了具體的值 * 那麼這些值的個數就是構造方法參數的個數 */ int minNrOfArgs; if (explicitArgs != null) { minNrOfArgs = explicitArgs.length; } else { /** * 實例化一個對象,用來存放構造方法的參數值 * 主要存放參數值和參數值對應的下標 */ ConstructorArgumentValues cargs = mbd.getConstructorArgumentValues(); // 這裏 經過 new ConstructorArgumentValues() 的方式來實例化一個空對象 resolvedValues = new ConstructorArgumentValues(); // 肯定構造方法的參數數量 // 在經過 Spring 內部給了一個值的狀況下 表示構造方法的最小參數個數 // 在沒有給的狀況下 爲 0 // mbd.getConstructorArgumentValues().addGenericArgumentValue(""); 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) { Class<?>[] paramTypes = candidate.getParameterTypes(); /** * 判斷是否肯定的具體的構造方法來完成實例化 * argsToUse.length > paramTypes.length * constructorToUse != null 表示已經有肯定的構造方法來 * argsToUse.length > paramTypes.length 說明要使用的參數與指定構造方法的參數個數不匹配 break 結束循環 * */ if (constructorToUse != null && argsToUse != null && argsToUse.length > paramTypes.length) { // Already found greedy constructor that can be satisfied -> // do not look any further, there are only less greedy constructors left. break; } // 當前構造方法使用的參數類型的個數,小於給定的構造方法的參數個數, // 說明當前的構造方法不匹配,結束本次循環,繼續下一次 if (paramTypes.length < minNrOfArgs) { continue; } ArgumentsHolder argsHolder; /** * 這裏 resolvedValues 必不爲空 * 由於 在前面的處理過程當中 explicitArgs 爲null * 因此 resolvedValues 經過 new ConstructorArgumentValues() 方式完成初始化 */ if (resolvedValues != null) { try { /** * 判斷是否加了ConstructorProperties註解,若是加了,則把值取出來 */ String[] paramNames = ConstructorPropertiesChecker.evaluate(candidate, paramTypes.length); if (paramNames == null) { ParameterNameDiscoverer pnd = this.beanFactory.getParameterNameDiscoverer(); if (pnd != null) { // 獲取構造方法的 參數列表 paramNames = pnd.getParameterNames(candidate); } } /** * Spring 內部只提供字符串的參數值,故而須要轉換 * argsHolder 所包含的值就是轉換以後的 */ 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 LinkedList<>(); } causes.add(ex); continue; } } else { // Explicit arguments given -> arguments length must match exactly. if (paramTypes.length != explicitArgs.length) { continue; } argsHolder = new ArgumentsHolder(explicitArgs); } /** * 定義了一個 類型差別量 typeDiffWeight: 這裏要注意 * isLenientConstructorResolution 默認爲true * 這行代碼的邏輯就是要肯定一個差別值 */ int typeDiffWeight = (mbd.isLenientConstructorResolution() ? argsHolder.getTypeDifferenceWeight(paramTypes) : argsHolder.getAssignabilityWeight(paramTypes)); // Choose this constructor if it represents the closest match. /** * 下面代碼是肯定一個一個最匹配的構造函數來 * 首先: * minTypeDiffWeight 是一個很大的值 typeDiffWeight 是一個負數, * 這也就是 Spring 前面爲構造方法排序的緣由,其實默認,參數最多的構造 public 構造方法是最合適的 * 確保當前的 if 分支可以進入 * 在if 分支中 給要使用的構造方法,以及要使用的參數作了賦值 * 其中 minTypeDiffWeight = typeDiffWeight 就是用來判斷哪一個構造方法更合適,可是這樣一來就會有一個問題, * 下一次的 for 循環進入到這裏,不知足 if 分支判斷條件 進入的 else if 中 * 當進入到 else if 中 * 說明有兩個構造函數比較合適,這個時候 spring 就不知道選哪一個構造函數來完成實例了 */ 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); } } 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)"); } 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 && argsHolderToUse != null) { argsHolderToUse.storeCache(mbd, constructorToUse); } } Assert.state(argsToUse != null, "Unresolved constructor arguments"); bw.setBeanInstance(instantiate(beanName, mbd, constructorToUse, argsToUse)); return bw; } 複製代碼
上述方法的過程分析複雜,而Spring爲了兼容開發人員在使用框架的過程當中可能會發生的各類場景,因此作了不少的判斷與處理。處理的場景多了 起來對應的異常狀況也就多類起來。關於這個方法的分析,我試着畫了一個流程圖處理,我打算從流程圖入手,試着梳理一下,若有錯誤之處,還請多多包涵!less
上述流程圖中藍顏色箭頭標出的爲主線流程,也是在平常開發中用到構造注入的大多數處理邏輯。在這裏分析這個流程圖,我準備拆分紅兩部分,第一部分就是藍顏色線條整個的流程;第二部分就是用綠顏色線條圈出來的部分,也是Spring中用來肯定合適的構造器的部分。我記得,我在 factory-method實例化對象的文章中「推斷構造器」的部分說到後面會分析,這裏就是。編輯器
// 表示最終要使用的構造器
Constructor<?> constructorToUse = null; // 表示最終構造器要使用的參數 Object[] argsToUse = null; 複製代碼
explicitArgs != null
爲
false
在這一步中會進行一些判斷,這些判斷中最終的目的就是爲了初始化 constructorToUse
與 argsToUse
。遺憾的是大多數的狀況下這一部分的判斷邏輯最後獲得的結果依然是這兩參數爲 null
。函數
constructorToUse == null || argsToUse == null
爲
true
Constructor<?>[] candidates = chosenCtors;
將上一篇文章中肯定的構造器賦於
candidates
;
candidates == null
我本身的理解是一種容錯機制,加入獲取到構造器爲
null
,Spring中會再次獲取一次。
@Autowired
注入的是無參構造器完成初始化,方法結束
@Autowired
注入的是有參數的構造器,方法流程繼續
4.判斷是否根據構造方法注入,@Autowired
注入構造器的方式,都是 true
。 post
5.肯定最小參數個數即 minNrOfArgs
的值,大多數狀況下都爲0。測試
❝若是在程序中經過 mbd.getConstructorArgumentValues().addGenericArgumentValue(""); 的方式設置,那麼這裏獲取到的就不是0。不多用到。flex
❞
AutowireUtils.sortConstructors(candidates);
對構造函數排序
//定義了一個最小類型差別變量
int minTypeDiffWeight = Integer.MAX_VALUE; // 有歧義的構造方法 Set<Constructor<?>> ambiguousConstructors = null; 複製代碼
差別變量的默認值是一個極大值。有歧義的構造方法集合默認爲 null
。
8.for
循環選出來的構造器
9.肯定類型差別變量typeDiffWeight
的值,經過 typeDiffWeight
與 最小類型差別變量minTypeDiffWeight
的值來肯定構造函數。
int typeDiffWeight = (mbd.isLenientConstructorResolution() ?
argsHolder.getTypeDifferenceWeight(paramTypes) : argsHolder.getAssignabilityWeight(paramTypes)); 複製代碼
這裏涉及到經過「寬鬆模式」與「嚴格模式」來肯定類型差別變量的值。這兩種模式這裏不是重點,後面在來看。總之咱們只須要知道,在這裏Spring肯定了一個值,來作比較。
經過「構造器」注入的方式默認採用的是寬鬆模式,這裏以我使用的例子爲例最終返回的是一個負數:「-1024」!
接下來就是找的更合適的構造器的時候了,請看下一小節...
推斷構造器,這裏的代碼也就「15行」,可是這部分的邏輯的確值我咱們分析一下,先看下圖:
@Service
public class DemoServiceOne { DemoServiceTwo demoServiceTwo; DemoServiceThree demoServiceThree; @Autowired(required = false) public DemoServiceOne(DemoServiceTwo demoServiceTwo){ this.demoServiceTwo = demoServiceTwo; } @Autowired(required = false) public DemoServiceOne(DemoServiceThree demoServiceThree, DemoServiceTwo demoServiceTwo){ this.demoServiceTwo = demoServiceTwo; this.demoServiceThree = demoServiceThree; } } 複製代碼
typeDiffWeight
= -1024」;
「minTypeDiffWeight
= 2147483647」。
if()
條件成立成立,作了下面幾件事:
constructorToUse = candidate
當前構造器(
「public的且參數最多的」)
argsToUse = argsHolder.arguments;
最終使用的參數賦值
minTypeDiffWeight = typeDiffWeight;
重置最小差別變量,用於後面的判斷
ambiguousConstructors = null;
重置有歧義的構造方法集合,固然第一次進入這裏自己就是
null
。
注意,此次循環排在最前面的是public
且「參數最多」的構造器。
for
循環中,在上述測試代碼的情形下,會進入下面的判斷語句中,所以結束循環。
if (constructorToUse != null && argsToUse != null && argsToUse.length > paramTypes.length) {
// Already found greedy constructor that can be satisfied -> // do not look any further, there are only less greedy constructors left. break; } 複製代碼
@Service
public class DemoServiceOne { DemoServiceTwo demoServiceTwo; DemoServiceThree demoServiceThree; @Autowired(required = false) public DemoServiceOne(DemoServiceTwo demoServiceTwo){ this.demoServiceTwo = demoServiceTwo; } @Autowired(required = false) public DemoServiceOne(DemoServiceThree demoServiceThree){ this.demoServiceThree = demoServiceThree; } } 複製代碼
for
循環與上面第一個測試例子是相同的處理邏輯,這裏不在贅述。
if()
條件中
argsToUse.length > paramTypes.length
這個判斷不成立,所以循環繼續。以下圖:
能夠看出,在有歧義的構造器中,最終添加了兩個構造器。如有其餘的構造器,上述的
if()
條件成立,所以循環終止。
else if
沒有對
constructorToUse
進行處理。
「首先這裏須要指出:」 @Configuration
類中的@Bean
方法的處理是嚴格模式。
Spring這兩種模式在本文中有兩處用到:①肯定類型差別變量;②:在有,有歧義的構造器時作相應的校驗。在肯定最終的構造器的時候,我的理解:寬鬆模式就是檢查條件更爲寬鬆,只要有符合的便放過。與之對應的嚴格模式則校驗條件更爲嚴格,只要不符合,則拋出異常。
下面主要來分析一下,兩種不一樣模式下如何肯定類型差別變量的。
public int getTypeDifferenceWeight(Class<?>[] paramTypes) {
// 與轉換以前的參數做比較,肯定一個差別值 int typeDiffWeight = MethodInvoker.getTypeDifferenceWeight(paramTypes, this.arguments); // 與轉換以後的參數做比較,肯定一個差別值 int rawTypeDiffWeight = MethodInvoker.getTypeDifferenceWeight(paramTypes, this.rawArguments) - 1024; return (rawTypeDiffWeight < typeDiffWeight ? rawTypeDiffWeight : typeDiffWeight); } 複製代碼
經過getTypeDifferenceWeight()
來肯定類型差別變量。
{
int result = 0; for (int i = 0; i < paramTypes.length; i++) { if (!ClassUtils.isAssignableValue(paramTypes[i], args[i])) { // 只要有一個參數類型不匹配,返回最大權重值 return Integer.MAX_VALUE; } if (args[i] != null) { Class<?> paramType = paramTypes[i]; Class<?> superClass = args[i].getClass().getSuperclass(); // 父類不爲null while (superClass != null) { // 注入參數的類型 是方法參數類型的子類,每往上找一層子類 // 差別值 +2,一直找到與方法參數的類型相同 if (paramType.equals(superClass)) { result = result + 2; superClass = null; } else if (ClassUtils.isAssignable(paramType, superClass)) { result = result + 2; superClass = superClass.getSuperclass(); } else { superClass = null; } } // 方法參數類型是接口 +1 if (paramType.isInterface()) { result = result + 1; } } } return result; } 複製代碼
public int getAssignabilityWeight(Class<?>[] paramTypes) {
for (int i = 0; i < paramTypes.length; i++) { // 首先判斷參數爲轉換以前的 if (!ClassUtils.isAssignableValue(paramTypes[i], this.arguments[i])) { // 只要有一個參數類型不匹配 就返回 最大的權重值 return Integer.MAX_VALUE; } } // 與轉換以後的參數類型匹配 for (int i = 0; i < paramTypes.length; i++) { if (!ClassUtils.isAssignableValue(paramTypes[i], this.rawArguments[i])) { // 有一個參數不匹配 就用最大的權重值減去 512 return Integer.MAX_VALUE - 512; } } // 到這裏 就是最匹配的,能夠看出最小匹配值是 最大匹配值 - 1024。能夠看出,權重匹配值的範圍 就是 0 ~ 1024 return Integer.MAX_VALUE - 1024; } 複製代碼
本文主要目的是嘗試着解釋一下,Spring是如何推薦構造器的。主要是畫出了肯定更合適的構造器的「流程圖」。
而後根據不一樣的 「肯定的構造器」的場景,來分析了一下Spring中的處理邏輯。
最後就是以前一直提到的 「寬鬆模式」與「嚴格模式」(ps:這裏對於這兩中方式的理解不是很到位,之後若是有更好的理解在作補充)
本文使用 mdnice 排版