大多數人只是停留在對 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的自動注入理解錯了!(範例→源碼證明)
當然,最重要的,先把我提到的知識點,背過!!!