走過路過Spring推斷構造器不要錯過哦~

找到更合適的構造器

1.沒有最合適的,只有更合適的

  上一篇文章中,咱們說到了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中的: 肯定構造器_02app

  接下來就是本文的重點,經過構造器來完成對象的實例化。再有多個構造器的時候,是如何選出最合適的一個呢?框架

2.經過構造器來完成對象的實例化

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

2.1 流程圖

肯定更合適的構造器流程圖
肯定更合適的構造器流程圖

  上述流程圖中藍顏色箭頭標出的爲主線流程,也是在平常開發中用到構造注入的大多數處理邏輯。在這裏分析這個流程圖,我準備拆分紅兩部分,第一部分就是藍顏色線條整個的流程;第二部分就是用綠顏色線條圈出來的部分,也是Spring中用來肯定合適的構造器的部分。我記得,我在 factory-method實例化對象的文章推斷構造器的部分說到後面會分析,這裏就是。編輯器

2.2 上述方法主流程

  • 0.初始化參數
// 表示最終要使用的構造器
 Constructor<?> constructorToUse = null;  // 表示最終構造器要使用的參數  Object[] argsToUse = null; 複製代碼
  1. explicitArgs != nullfalse

  在這一步中會進行一些判斷,這些判斷中最終的目的就是爲了初始化 constructorToUseargsToUse。遺憾的是大多數的狀況下這一部分的判斷邏輯最後獲得的結果依然是這兩參數爲 null函數

  1. constructorToUse == null || argsToUse == nulltrue
  • Constructor<?>[] candidates = chosenCtors; 將上一篇文章中肯定的構造器賦於 candidates;
  • candidates == null我本身的理解是一種容錯機制,加入獲取到構造器爲 null,Spring中會再次獲取一次。
  1. 當肯定的構造器只有一個的時候
    • @Autowired注入的是無參構造器完成初始化,方法結束 注入無參數的構造器
    • @Autowired注入的是有參數的構造器,方法流程繼續 注入有參數的構造器

4.判斷是否根據構造方法注入,@Autowired注入構造器的方式,都是 true自定注入標誌post

5.肯定最小參數個數即 minNrOfArgs 的值,大多數狀況下都爲0。測試

若是在程序中經過 mbd.getConstructorArgumentValues().addGenericArgumentValue(""); 的方式設置,那麼這裏獲取到的就不是0。不多用到。flex

  1. AutowireUtils.sortConstructors(candidates);對構造函數排序
  • 示例代碼: 代碼
  • 排序以前: 排序前
  • 排序以後: 排序以後 7.定義參數用於肯定構造函數
//定義了一個最小類型差別變量
 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! 類型差別變量

接下來就是找的更合適的構造器的時候了,請看下一小節...

2.3 推斷構造器

  推斷構造器,這裏的代碼也就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;  }  }  複製代碼
  1. 第一次循環,以本文測試方法爲例 typeDiffWeight = -1024; minTypeDiffWeight = 2147483647if()條件成立成立,作了下面幾件事:
  • constructorToUse = candidate當前構造器( public的且參數最多的)
  • argsToUse = argsHolder.arguments; 最終使用的參數賦值
  • minTypeDiffWeight = typeDiffWeight; 重置最小差別變量,用於後面的判斷
  • ambiguousConstructors = null; 重置有歧義的構造方法集合,固然第一次進入這裏自己就是 null

  注意,此次循環排在最前面的是public參數最多的構造器。

  1. 第二次 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;  }  } 複製代碼
  1. 第一次 for循環與上面第一個測試例子是相同的處理邏輯,這裏不在贅述。
  2. 第二次循環,因爲兩個構造函數的參數個數相同,因此上述的 if()條件中 argsToUse.length > paramTypes.length這個判斷不成立,所以循環繼續。以下圖: 同類型的構造器 能夠看出,在有歧義的構造器中,最終添加了兩個構造器。如有其餘的構造器,上述的 if() 條件成立,所以循環終止。
  3. 而後,Spring要對有有歧義的構造器列表作處理 有歧義的構造器處理 能夠看出,在 嚴格模式下,若是有兩個可用的構造器,Spring會直接拋出異常。而在默認的狀況下,Spring使用的是 寬鬆模式,所以方法繼續。可是這樣一來,Spring到底會使用哪一個構造器呢?答案就在上述的源碼中,那就是 第一次循環的構造器。由於第二次的循環,在 else if沒有對 constructorToUse進行處理。 最終使用的構造器

3.關於嚴格/寬鬆 模式

   首先這裏須要指出: @Configuration類中的@Bean方法的處理是嚴格模式。

  Spring這兩種模式在本文中有兩處用到:①肯定類型差別變量;②:在有,有歧義的構造器時作相應的校驗。在肯定最終的構造器的時候,我的理解:寬鬆模式就是檢查條件更爲寬鬆,只要有符合的便放過。與之對應的嚴格模式則校驗條件更爲嚴格,只要不符合,則拋出異常。

  下面主要來分析一下,兩種不一樣模式下如何肯定類型差別變量的。

3.1寬鬆模式

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;  } 複製代碼

3.2嚴格模式

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;  } 複製代碼

4.總結

  本文主要目的是嘗試着解釋一下,Spring是如何推薦構造器的。主要是畫出了肯定更合適的構造器的流程圖

  而後根據不一樣的 肯定的構造器的場景,來分析了一下Spring中的處理邏輯。

  最後就是以前一直提到的 寬鬆模式嚴格模式(ps:這裏對於這兩中方式的理解不是很到位,之後若是有更好的理解在作補充)

本文使用 mdnice 排版

相關文章
相關標籤/搜索