99%的人答不對Spring推斷構造方法!打破你三觀!!!(範例→源碼)

大多數人只是停留在對 Spring 的簡單應用上,所以一般也不會瞭解到 Spring 的構造方法注入。

其實在 Spring 的官網中明確說到:

  • Spring 推薦對於那些必須依賴注入的屬性,使用構造方法注入;
  • 而那些不一定非要注入的屬性,Spring 則推薦使用 setter 注入。

所以,既然 Spring 官網都那麼說了,你要是連構造方法注入都不好好學習,那可就有點對不起自己啦。
在這裏插入圖片描述
既然要學習 Spring 的構造方法注入,我們先不研究源碼,我們先從基本的使用開始:

我先給出一個 A 類,裏面有各種各樣的構造方法。
然後,你可以自己先思考,Spring 會調用哪個構造方法。
然後,看我的測試,看看你想的和 Spring 想的,到底一樣不一樣!

@Component
public class A {
	public A() {
		System.out.println("無參");
	}
	public A(B b) {
		System.out.println("B");
	}
	public A(C c) {
		System.out.println("C");
	}
	public A(String str) {
		System.out.println("str");
	}
	public A(B b, C c) {
		System.out.println("B,C");
	}
	public A(B b, C c, String str) {
		System.out.println("B,C,str");
	}
}

我要往 Spring 容器中添加一個 B 一個 C,這樣才能夠給構造方法傳參:

@Component
public class B {
}
@Component
public class C {
}

這裏我使用註解驅動,初始化 Spring 容器:

public static void main(String[] args) {
	ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class);
}

在這裏插入圖片描述
不知道你猜對了沒有,即使猜錯了也不要緊,我會慢慢分析的;
不過,假設你僥倖猜對了,那也不要高興,看看能不能接住面試官的連環轟炸!

@Component
public class A {
	public A() {
		System.out.println("無參");
	}
}

在這裏插入圖片描述

@Component
public class A {
	public A() {
		System.out.println("無參");
	}
	public A(B b) {
		System.out.println("B");
	}
}

在這裏插入圖片描述

@Component
public class A {
	public A() {
		System.out.println("無參");
	}
	public A(B b) {
		System.out.println("B");
	}
	public A(C c) {
		System.out.println("C");
	}
}

在這裏插入圖片描述

@Component
public class A {
	public A() {
		System.out.println("無參");
	}
	public A(B b) {
		System.out.println("B");
	}
	public A(String str) {
		System.out.println("str");
	}
}

在這裏插入圖片描述

@Component
public class A {
	public A(B b) {
		System.out.println("B");
	}
}

在這裏插入圖片描述

@Component
public class A {
	public A(B b) {
		System.out.println("B");
	}
	public A(C c) {
		System.out.println("C");
	}
}

在這裏插入圖片描述

@Component
public class A {
	public A(B b) {
		System.out.println("B");
	}
	public A(String str) {
		System.out.println("str");
	}
}

在這裏插入圖片描述
想來你慢慢地已經開始自己發現規律了。
現在,我繼續增加新的條件:

@Component
public class A {
	public A() {
		System.out.println("無參");
	}
	public A(B b) {
		System.out.println("B");
	}
	// 增加@Autowired註解
	@Autowired
	public A(C c) {
		System.out.println("C");
	}
	public A(String str) {
		System.out.println("str");
	}
	public A(B b, C c) {
		System.out.println("B,C");
	}
	public A(B b, C c, String str) {
		System.out.println("B,C,str");
	}
}

在這裏插入圖片描述

@Component
public class A {
	public A() {
		System.out.println("無參");
	}
	public A(B b) {
		System.out.println("B");
	}
	public A(C c) {
		System.out.println("C");
	}
	public A(String str) {
		System.out.println("str");
	}
	// 增加@Autowired註解
	@Autowired
	public A(B b, C c) {
		System.out.println("B,C");
	}
	public A(B b, C c, String str) {
		System.out.println("B,C,str");
	}
}

在這裏插入圖片描述

@Component
public class A {
	public A() {
		System.out.println("無參");
	}
	public A(B b) {
		System.out.println("B");
	}
	public A(C c) {
		System.out.println("C");
	}
	// 增加@Autowired註解
	@Autowired
	public A(String str) {
		System.out.println("str");
	}
	public A(B b, C c) {
		System.out.println("B,C");
	}
	public A(B b, C c, String str) {
		System.out.println("B,C,str");
	}
}

在這裏插入圖片描述

@Component
public class A {
	public A() {
		System.out.println("無參");
	}
	@Autowired
	public A(B b) {
		System.out.println("B");
	}
	@Autowired
	public A(C c) {
		System.out.println("C");
	}
}

在這裏插入圖片描述

@Component
public class A {
	public A() {
		System.out.println("無參");
	}
	@Autowired(required = false)
	public A(B b) {
		System.out.println("B");
	}
	@Autowired
	public A(C c) {
		System.out.println("C");
	}
}

在這裏插入圖片描述

@Component
public class A {
	public A() {
		System.out.println("無參");
	}
	@Autowired(required = false)
	public A(B b) {
		System.out.println("B");
	}
	@Autowired(required = false)
	public A(C c) {
		System.out.println("C");
	}
}

在這裏插入圖片描述

@Component
public class A {
	@Autowired(required = false)
	public A() {
		System.out.println("無參");
	}
	@Autowired(required = false)
	public A(B b) {
		System.out.println("B");
	}
	public A(C c) {
		System.out.println("C");
	}
}

在這裏插入圖片描述
是不是發現,一開始也沒有想到?
當然,現在你也應該可以開始慢慢找到規律了。

不過,你以爲這就結束了嗎?
面試官一定會給你繼續連環轟炸!

現在,新增一個類D:

public class D implements I {
}
public interface I {
}

下面,給 xml 配置文件中加入如下配置,也就是給 a 設置基於構造方法的自動注入。

<bean id="a" class="com.jiang.bean.A" autowire="constructor">
</bean>

<bean id="b" class="com.jiang.bean.B">
</bean>

<bean id="c" class="com.jiang.bean.C">
</bean>

<bean id="d" class="com.jiang.bean.D">
</bean>

然後依據 xml 配置初始化容器:

public static void main(String[] args) {
	ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
}

下面繼續進行判斷:

public class A {
	@Autowired
	public A() {
		System.out.println("無參");
	}
	public A(B b) {
		System.out.println("B");
	}
	public A(C c) {
		System.out.println("C");
	}
	public A(D d) {
		System.out.println("D");
	}
	public A(B b, C c) {
		System.out.println("B,C");
	}
	public A(B b, C c, D d) {
		System.out.println("B,C,D");
	}
}

在這裏插入圖片描述

public class A {
	public A() {
		System.out.println("無參");
	}
	public A(B b) {
		System.out.println("B");
	}
}

在這裏插入圖片描述

public class A {
	public A() {
		System.out.println("無參");
	}
	public A(B b) {
		System.out.println("B");
	}
	public A(C c) {
		System.out.println("C");
	}
}

在這裏插入圖片描述

public class A {
	public A() {
		System.out.println("無參");
	}
	public A(B b) {
		System.out.println("B");
	}
	public A(C c) {
		System.out.println("C");
	}
	public A(D d) {
		System.out.println("D");
	}
}

在這裏插入圖片描述

public class A {
	public A() {
		System.out.println("無參");
	}
	public A(C c) {
		System.out.println("C");
	}
	public A(I i) {
		System.out.println("I");
	}
}

在這裏插入圖片描述

public class A {
	public A() {
		System.out.println("無參");
	}
	public A(D d) {
		System.out.println("D");
	}
	public A(I i) {
		System.out.println("I");
	}
}

在這裏插入圖片描述
是不是如果我不給你提,你打死也想不到,Spring 光一個構造方法,就有這麼多的條件要去判斷,可以衍生出這麼多的題給你做!!!

看到這裏,我想你也差不多可以自己總結出一些規律。

  • 就是在沒有設置自動注入爲構造方法注入的時候,會默認使用無參構造方法;
  • 如果沒有無參構造方法,那就會使用唯一的那個構造方法;
  • 如果提供了不止一個構造方法,但沒有無參構造方法,Spring 就會迷茫,所以會拋出異常;
  • 如果加了 @Autowired 註解,就會指定那個構造方法;
  • 如果,加了多個 @Autowired 註解,除非全都是 required=false,否則也會拋異常;
  • 加了多個 @Autowired 註解並且全部是 required=false 的話,那就會從這些構造方法中選擇一個最合適的;
  • 如果是構造方法自動注入,那麼就會從多個構造方法中,選出一個最合適的。

但是,如果用 @Autowired 指定了多個,或者開啓了構造方法的自動注入,那麼選出的最合適的又是哪個?
根據現象來看:

  • 首先,是參數個數最多的(當然得除去有無效參數的構造方法,比如沒有往容器中添加的字符串等待);
  • 如果參數個數一樣,那麼就選參數實現了接口的;
  • 如果參數直接就是接口,那麼優先級沒有類級別高;
  • 如果都一樣了,那麼按照字符串的排列順序,a、b、c…選出最前的。

說了這麼多,不過,這些都是現象對不對,我還沒有給你們分析源碼。
那麼,接下來,我們就要先找到 Spring 推斷構造方法的代碼。

既然什麼時候會推斷構造方法呢?
那肯定是要創建對象的時候,要去推斷構造方法(比較構造方法是用來創建對象的)。
於是,我們在 Spring 中找到 createBeanInstance(beanName, mbd, args); 這個方法。

點進去,我們可以看到:
在這裏插入圖片描述
在 determineConstructorsFromBeanPostProcessors 這個方法處,返回了一個 構造方法數組;
而且,這個方法的名字,翻譯成中文,就叫決定構造方法。

那麼,很有可能,就是在這個方法中,推斷出了要使用的構造方法。
於是,我們 debug 一下:
此時採用註解驅動開發,且 A 的代碼爲:

@Component
public class A {
	public A() {
		System.out.println("無參");
	}
	public A(B b) {
		System.out.println("B");
	}
	public A(B b, C c) {
		System.out.println("B,C");
	}
}

然後,開始 debug:
在這裏插入圖片描述
你會發現,它返回了一個 null。

是不是覺得很奇怪,怎麼它推斷不出構造方法嗎?
按照道理,此時沒有使用自動構造方法注入,那應該返回一個無參構造方法纔對,可是怎麼只有一個 null?

於是,我讓你們抱着懷疑的心態,再試一試:
這回,只有一個無參構造方法了,Spring 總能推斷的出來吧。

@Component
public class A {
	public A() {
		System.out.println("無參");
	}
}

在這裏插入圖片描述
總感覺不太對勁是吧,於是,再讓你抱着懷疑的心態,再給你測試測試:

@Component
public class A {
	public A(B b) {
		System.out.println("B");
	}
}

在這裏插入圖片描述
然後,你就會發現 Spring 把構造方法找出來了。
你肯能有很多問號,不過沒關係,我再給你加一個問號:

@Component
public class A {
	public A() {
		System.out.println("無參");
	}
	public A(B b) {
		System.out.println("B");
	}
	@Autowired
	public A(B b, C c) {
		System.out.println("B,C");
	}
	public A(B b, C c, D d) {
		System.out.println("B,C,D");
	}
}

在這裏插入圖片描述
你會發現,這次 Spring 又把構造方法找出來了,而且就是 @Autowired 指定的那個構造方法。

現在也許你很疑惑,不過不要擔心,我來告訴你答案。
既然要探究 Spring 的做法,那我們就來分析源碼:

---- 想了想,這次還是用源碼+註釋的形式吧,不會閱讀代碼的小夥伴,就揹我的結論,面試的時候就跟面試官直接噴,說 Spring 源碼是這麼這麼寫的。。。。
---- 我給的細節很全,只要你記住了,一般不會碰到一些搞不定的面試題(我只是指推斷構造方法相關的)。

我先解釋一下,爲什麼上面的推斷構造方法,給出的返回值爲 null。
因爲,如果最後沒有找出構造方法的話,Spring 就會使用默認無參構造方法。
所以,返回 null,就表示,要使用無參構造方法,Spring 會在後面的代碼進行調用無參構造方法進行創建對象。

注意!!!:這只是在沒有開啓構造方法自動注入的情況下!!!
假設開啓了構造方法的自動注入,在這裏返回 null 的時候,還會接着繼續找構造方法。

---- 如果你直接看源碼,可能比較費力,我先給你描述一遍流程,你再閱讀源碼,就會輕鬆很多。
---- 所有和 Kotlin 有關的代碼都不去管,我沒學過 Kotlin,也不用它做開發。

  • 首先是 lookup 的檢查,這個不是這一篇文章的重點,所以我的代碼不會貼這段
    (畢竟和推斷構造方法沒關係)
  • 先從緩存中獲取,如果之前已經解析過構造方法了,那麼此時就不用再解析,直接使用之前解析過的構造方法即可(一般只有原型的 bean 會創建多次,所以原型的 bean 會利用到緩存)。
  • 先拿到所有的構造方法
  • 遍歷所有的構造方法
  • 獲取構造方法的 @Autowired 的封裝對象
  • 如果多個 @Autowired 都是 required=true,就會拋異常
  • 如果構造方法有 @Autowired ,就會存入 List
  • 如果有 @Autowired 註解,並且 required=true,那麼就會返回這一個構造方法
  • 如果有 @Autowired 註解,並且都是 required=false,那麼就會返回這些構造方法和一個無參構造方法的數組
  • 如果沒有 @Autowired 註解,只有一個有參構造方法,就會會這一個構造方法
  • 如果都不是,就會返回 null

下面,我們就來看推斷構造方法的源代碼:
(雖然我寫了很多註釋,不過如果發現看不下去了,那至少背上面的流程,和背結論)

@Override
@Nullable
public Constructor<?>[] determineCandidateConstructors(Class<?> beanClass, final String beanName)
		throws BeanCreationException {

	// Let's check for lookup methods here...
	// lookup相關我直接省略

	// Quick check on the concurrent map first, with minimal locking.
	// 這裏是用於緩存,如果推斷過構造方法了,那麼就會保存在一個Map中,
	// 那麼下一次來創建對象的時候,就直接返回Map中保存的,就不用再次推斷構造方法了。
	// 從Map中取
	Constructor<?>[] candidateConstructors = this.candidateConstructorsCache.get(beanClass);
	// 如果Map中取出的爲null,說明沒有創建過對象,此時要推斷構造方法
	if (candidateConstructors == null) {
		// Fully synchronized resolution now...
		// 加鎖保證線程安全(雙檢鎖)
		synchronized (this.candidateConstructorsCache) {
			candidateConstructors = this.candidateConstructorsCache.get(beanClass);
			if (candidateConstructors == null) {
				Constructor<?>[] rawCandidates;
				try {
					// 先拿到所有的構造方法
					rawCandidates = beanClass.getDeclaredConstructors();
				}
				catch (Throwable ex) {
					拋異常
				}
				// 這個list用於存放加了@Autowired註解的構造方法
				List<Constructor<?>> candidates = new ArrayList<>(rawCandidates.length);
				// 加了@Autowired註解的構造方法,如果required==true,就用這個變量存放
				Constructor<?> requiredConstructor = null;
				// 用於存放默認無參構造方法
				Constructor<?> defaultConstructor = null;
				// 這個primaryConstructor是用來返回Kotlin類的主要構造方法
				// 但是如果是Java類,則只會返回null
				// 和Kotlin相關,所以不研究
				Constructor<?> primaryConstructor = BeanUtils.findPrimaryConstructor(beanClass);
				// 不考慮合成方法,此處忽略
				int nonSyntheticConstructors = 0;
				// 遍歷所有的構造方法
				for (Constructor<?> candidate : rawCandidates) {
					// 如果是合成的構造方法(不要管這個)
					if (!candidate.isSynthetic()) {
						nonSyntheticConstructors++;
					}
					// 因爲用的是Java,所以這個primaryConstructor一定爲null,所以不用管
					else if (primaryConstructor != null) {
						continue;
					}
					// 如果構造方法加了@Autowired,就會獲取到ann
					AnnotationAttributes ann = findAutowiredAnnotation(candidate);
					if (ann == null) {
						Class<?> userClass = ClassUtils.getUserClass(beanClass);
						if (userClass != beanClass) {
							try {
							    // 因爲,如果有繼承關係,那麼父類可能會加@Autowired註解
							    // 所以嘗試獲取父類的構造方法
								Constructor<?> superCtor =
										userClass.getDeclaredConstructor(candidate.getParameterTypes());
								// 從父類那裏尋找是否有添加@Autowired註解
								ann = findAutowiredAnnotation(superCtor);
							}
							catch (NoSuchMethodException ex) {
								// Simply proceed, no equivalent superclass constructor found...
							}
						}
					}
					// ann != null,說明這個構造方法加了@Autowired註解
					// 根據拋異常的規則,如果有多個構造方法加了@Autowired註解,
					// 只要有required=true,就要拋異常。
					// 所以如果加了多個註解,那麼所有的註解都要required=false,纔不會拋異常
					if (ann != null) {
						// requiredConstructor != null,
						// 說明之前也有構造方法加了@Autowired註解,並且required=true,那就要拋異常
						if (requiredConstructor != null) {
							拋異常
						}
						boolean required = determineRequiredStatus(ann);
						if (required) {
							// 如果不爲空,那麼就說明之前有構造方法加了@Autowired註解,就要拋異常
							if (!candidates.isEmpty()) {
								拋異常
							}
							// 這時,這個requiredConstructor就會被賦值
							requiredConstructor = candidate;
						}
						// 那麼這個加了@Autowired註解的構造方法就會存入candidates這個list
						candidates.add(candidate);
					}
					// 如果參數是0,那麼就是無參構造方法,就賦值給defaultConstructor
					else if (candidate.getParameterCount() == 0) {
					    // defaultConstructor就是在這裏被找出賦值
						defaultConstructor = candidate;
					}
				}
				// 以爲上面找出有@Autowired註解的構造方法都被添加到了candidates這個List中
				// 所以此時不爲空,說明有加了@Autowired註解的構造方法
				if (!candidates.isEmpty()) {
					// Add default constructor to list of optional constructors, as fallback.
					// 如果@Autowired註解全是required=false,且額外有無參構造方法,就再加一個無參構造方法,
					// 所以candidates就一定有多個構造方法
					// 反之,要是required=true,那就不會進if
					// 那candidates中就只會有那一個確定的構造方法
					if (requiredConstructor == null) {
						if (defaultConstructor != null) {
							candidates.add(defaultConstructor);
						}
						else if (candidates.size() == 1 && logger.isInfoEnabled()) {
							打印日誌
						}
					}
					// List轉化爲數組
					candidateConstructors = candidates.toArray(new Constructor<?>[0]);
				}
				// 上面完成了所有構造方法的判斷
				// 如果只有一個構造方法,並且參數>0
				// 就是隻有一個有參構造方法
				else if (rawCandidates.length == 1 && rawCandidates[0].getParameterCount() > 0) {
					// 返回的數組,就是那一個構造方法
					candidateConstructors = new Constructor<?>[] {rawCandidates[0]};
				}
				// 這裏由於不牽扯kotlin,所以primaryConstructor一定爲null,所以不用考慮
				else if (nonSyntheticConstructors == 2 && primaryConstructor != null &&
						defaultConstructor != null && !primaryConstructor.equals(defaultConstructor)) {
					candidateConstructors = new Constructor<?>[] {primaryConstructor, defaultConstructor};
				}
				else if (nonSyntheticConstructors == 1 && primaryConstructor != null) {
					candidateConstructors = new Constructor<?>[] {primaryConstructor};
				}
				// 有一個無參構造方法,或者有多個構造方法
				// candidateConstructors就會是長度爲0的數組
				// 所以在只有無參構造方法的時候,會返回null,
				// 如果有多個構造方法,只要沒加@Autowired註解,也返回null
				else {
					candidateConstructors = new Constructor<?>[0];
				}
				// 最後加入緩存
				this.candidateConstructorsCache.put(beanClass, candidateConstructors);
			}
		}
	}
	// 如果數組長度爲0,就返回空
	// 如果長度不爲0,說明有@Autowired註解的方法,
	// 或者也可能是隻有一個有參構造方法
	return (candidateConstructors.length > 0 ? candidateConstructors : null);
}

這就是 Spring 第一次推斷構造方法,不過只是一個普通的,不考慮自動注入,配置參數的推斷!
來看總結的返回結果:

  • 如果爲空,說明有多個構造方法,或者只有一個默認構造方法
  • 如果不爲空,那就可能就是只有一個有參構造方法,那麼就會執行這個構造方法
  • 也可能只有一個 @Autowired 註解,並且 required=true
  • 也有可能返回了多個構造方法,說明有構造方法加了 @Autowired 註解,並且全部都是 required=false

不過,由於第一次推斷,是不考慮自動注入,配置信息的。
所以,還可能有第二次的判斷,最終決定。

// 這裏第一次推斷了構造方法
// 如果爲空,說明有多個構造方法,或者只有一個默認構造方法
// 如果不爲空,那就可能就是隻有一個有參構造方法,那麼就會執行這個構造方法
// 也可能只有一個@Autowired註解,並且required=true
// 也有可能返回了多個構造方法,說明有構造方法加了@Autowired註解,並且全部都是required=false
Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
// 如果不爲空的話,就會進入if來進行對象的創建
// 不過如果爲空,但是自動注入模型爲構造方法自動注入,也會調用方法再次決定構造方法去執行
// 或者,如果配置中指定了構造方法的參數,那麼也會進入該方法
if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR ||
		mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {
	return autowireConstructor(beanName, mbd, ctors, args);
}

那麼,我們就要接着研究,autowireConstructor 這個方法。

return new ConstructorResolver(this).autowireConstructor(beanName, mbd, ctors, explicitArgs);

可以發現,這個方法,就是調用了另一個方法,我們繼續點進去:

public BeanWrapper autowireConstructor(String beanName, RootBeanDefinition mbd,
		@Nullable Constructor<?>[] chosenCtors, @Nullable Object[] explicitArgs) {
	// 初始化bw,不要用管
	BeanWrapperImpl bw = new BeanWrapperImpl();
	this.beanFactory.initBeanWrapper(bw);

	// 最後被確定要使用的構造方法
	Constructor<?> constructorToUse = null;
	// 存放構造方法使用的參數
	ArgumentsHolder argsHolderToUse = null;
	// 最後被確定使用的參數
	Object[] argsToUse = null;

	// explicitArgs通過getBean方法傳入,如果getBean方法調用的時候指定方法參數那麼直接使用
	// 一般只有原型會手動getBean,可以指定傳入參數。
	// 而單例bean在容器刷新時就有了,所以getBean傳入參數沒什麼作用,不會再來創建
	// 所以,這裏就是null,我們繼續往後看
	if (explicitArgs != null) {
		argsToUse = explicitArgs;
	}
	else {
		// 如果在getBean方法沒有指定則嘗試從配置文件中解析,xml文件可以指定構造方法的參數
		Object[] argsToResolve = null;
		//嘗試從緩存中獲取
		synchronized (mbd.constructorArgumentLock) {
			// 已經解析過的構造方法或工廠方法,一般只有原型纔會不爲null,第一次創建解析,以後就不用再解析了
			constructorToUse = (Constructor<?>) mbd.resolvedConstructorOrFactoryMethod;
			if (constructorToUse != null && mbd.constructorArgumentsResolved) {
				// Found a cached constructor...
				// 直接使用之前解析過的參數
				argsToUse = mbd.resolvedConstructorArguments;
				if (argsToUse == null) {
					// 配置的構造函數參數(因爲配置的參數value都是String,所以需要解析)
					argsToResolve = mbd.preparedConstructorArguments;
				}
			}
		}
		if (argsToResolve != null) {
			// 解析參數類型
			argsToUse = resolvePreparedArguments(beanName, mbd, bw, constructorToUse, argsToResolve, true);
		}
	}

	// 說明沒有用getBean傳參,也沒有在配置文件中配置
	// 那麼就會在後面開始選擇構造方法開始執行
	if (constructorToUse == null || argsToUse == null) {
		// Take specified constructors, if any.
		// 這裏是之前第一次選出的構造方法數組
		Constructor<?>[] candidates = chosenCtors;
		// ==null說明之前無法決定構造方法,說明需要構造方法自動注入模型來推斷構造方法
		if (candidates == null) {
			Class<?> beanClass = mbd.getBeanClass();
			try {
				// 如果能訪問非public的構造方法,那就獲取所有的包括非public的構造方法,
				// 否則,只獲取public的構造方法
				candidates = (mbd.isNonPublicAccessAllowed() ?
						beanClass.getDeclaredConstructors() : beanClass.getConstructors());
			}
			catch (Throwable ex) {
				拋異常
			}
		}
		// candidates中只有一個構造方法,說明可能之前已經選出了構造方法
		// 說明可能只有一個有參構造方法,或者有一個@Autowired指定了構造方法
		// 也可能是隻有一個默認無參構造方法,在上面被獲取到
		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;
		// 我們一般不會調用getBean傳參創建時使用
		if (explicitArgs != null) {
			minNrOfArgs = explicitArgs.length;
		}
		else {
			// 提取配置文件中的配置的構造函數參數
			ConstructorArgumentValues cargs = mbd.getConstructorArgumentValues();
			// 用於承載解析後的構造函數參數的值
			resolvedValues = new ConstructorArgumentValues();
			// 需要的最小參數值,配置了的參數的個數
			// 因爲配置了那麼多參數了,總不能還調用一個參數比配置的參數個數少的構造方法吧
			minNrOfArgs = resolveConstructorArguments(beanName, mbd, bw, cargs, resolvedValues);
		}

		// 排序給定的構造函數,public在前,非public在後
		// 按照構造方法的優先級進行排序
		// 由於這是Spring弄得優先級很複雜,不適合閱讀,不過我們通過之前的測試代碼,也發現了規律
		AutowireUtils.sortConstructors(candidates);
		// 這個變量用於記錄最小的差異權重
		int minTypeDiffWeight = Integer.MAX_VALUE;
		// 如果有權重相等,那麼放到這個模棱兩可的集合中
		Set<Constructor<?>> ambiguousConstructors = null;
		// 發生異常不拋出,先存入這個List中
		LinkedList<UnsatisfiedDependencyException> causes = null;

		// 這裏就會按照剛纔的優先級順序進行遍歷
		for (Constructor<?> candidate : candidates) {
			Class<?>[] paramTypes = candidate.getParameterTypes();

			// 如果之前選用的構造方的的參數的個數 > 當前構造方法的參數個數,就break終止循環
			// 因爲排序有順序的,參數個數多的排在最前面
			// 所以,一般只要構造方法符合要求,Spring會自動選用最長的構造方法
			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;
			// 如果封裝的參數對象不爲空
			if (resolvedValues != null) {
				try {
					String[] paramNames = ConstructorPropertiesChecker.evaluate(candidate, paramTypes.length);
					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) {
					if (logger.isTraceEnabled()) {
						logger.trace("Ignoring constructor [" + candidate + "] of bean '" + beanName + "': " + ex);
					}
					// Swallow and try next constructor.
					if (causes == null) {
						causes = new LinkedList<>();
					}
					// 將異常存入List中
					causes.add(ex);
					continue;
				}
			}
			else {
				// Explicit arguments given -> arguments length must match exactly.
				if (paramTypes.length != explicitArgs.length) {
					continue;
				}
				argsHolder = new ArgumentsHolder(explicitArgs);
			}

			// 獲取差異權重值
			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;
			}
			// 如果它的類型差異權重和之前的一個最小的類型差異權重值相等
			// 就把這個構造方法添加到一個模棱兩可構造方法set中,表示這些構造方法無法根據權重選出最優
			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;
			}
			拋異常
		}
		// 如果模棱兩可構造方法集合不爲空,並且不是寬鬆解析構造方法的話,就要拋異常
		// (一般默認是寬鬆的,所以遇到模棱兩可直接使用排在前面的)
		else if (ambiguousConstructors != null && !mbd.isLenientConstructorResolution()) {
			拋異常
		}
		
		// 都不爲空,說明已經解析到了要使用的構造方法
		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;
}

源碼就這麼多,可以說確實沒有什麼難度,我對第二次的方法做一個小小的總結:

  • 如果之前解析過,緩存中有,就直接使用之前解析過的
    (一般只有原型 bean 會創建多次,纔會利用到緩存)
  • 如果之前的推斷的構造法只有一個,那就會直接使用這一個,然後 return
  • 如果之前推斷出了構造方法不止一個,那麼就會遍歷這些構造方法,來進行挑選
  • 否則,就會遍歷所有的構造方法
  • 如果在 getBean 傳參,就會設置一個最小的參數個數要求,構造方法的個數必須比傳參的個數多
  • 或者,如果配置了參數,則也會要求構造方法最小的參數個數要是配置的參數個數
  • 在遍歷之前,會先進行排序,public 會排在前面,參數個數多的會排在前面
    (這也是 Spring 構造方法自動注入,會默認選擇最長的構造方法的原因)
  • 遍歷的時候,如果獲取參數值拋異常,會先被 catch 起來,不拋出,等到最後遍歷結束了,如果沒有找到要用的構造方法,纔會拋出異常
  • 遍歷的時候,會計算這個構造方法的差異權重,差異權重最小的,會被選擇上
  • 遍歷的時候,如果發現遍歷的這一個構造方法的參數比之前遍歷選擇小,就break終止遍歷
    (這也是 Spring 構造方法自動注入,會默認選擇最長的構造方法的原因,因爲找到短的就不再遍歷了,就只用之前長的)
  • 如果有兩個構造方法,差異權重一樣,那麼就會存到一個集合中,表示模棱兩可,不知道選誰好
  • 遍歷結束後,如果沒有找到要使用的構造方法,就會拋異常
  • 遍歷結束後,如果發現模棱兩可集合中有值,說明存在模棱兩可的構造方法,如果解析構造函數是寬鬆要求,那就使用排序的前一個;如果不寬鬆要求,非常嚴謹,那麼就會拋異常。
  • 解析的結果最後會加入緩存

構造方法的選擇,如果你閱讀到了這裏,那你一定會收益匪淺。

很多時候,由於開發者只寫了幾行代碼,就把 Spring 跑起來了,就認爲已經學會了。
其實 Spring 在背後還默默地做了很多很多事。

所以,對於框架的學習,絕對不能只稍稍使用,我們還應該,多去閱讀源碼,理清其中的實現思路,以及提供給使用者的擴展點。
這樣,才能更好地,對框架進行使用和擴展。

當然,Spring 由於除了構造方法注入,還會有基於 setter 的注入,關於自動注入,還有很多人抱有誤解。
所以,筆者也建議你再去看一下我的這篇有關自動注入的文章,相信你會獲益匪淺!
99%的人把Spring的自動注入理解錯了!(範例→源碼證明)

當然,最重要的,先把我提到的知識點,背過!!!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章