6.Spring之推斷構造方法源碼解析

推斷構造方法流程圖https://www.processon.com/view/link/5f97bc717d9c0806f291d7eb

AutowiredAnnotationBeanPostProcessor中推斷構造方法不同情況思維腦圖:https://www.processon.com/view/link/6146def57d9c08198c58bb26

Spring中的一個bean,需要實例化得到一個對象,而實例化就需要用到構造方法。

一般情況下,一個類只有一個構造方法:

  1. 要麼是無參的構造方法
  2. 要麼是有參的構造方法

如果只有一個無參的構造方法,那麼實例化就只能使用這個構造方法了。

如果只有一個有參的構造方法,那麼實例化時能使用這個構造方法嗎?要分情況討論:

  1. 使用AnnotationConfigApplicationContext,會使用這個構造方法進行實例化,那麼Spring會根據構造方法的參數信息去尋找bean,然後傳給構造方法
  2. 使用ClassPathXmlApplicationContext,表示使用XML的方式來使用bean,要麼在XML中指定構造方法的參數值(手動指定),要麼配置autowire=constructor讓Spring自動去尋找bean做爲構造方法參數值。

上面是隻有一個構造方法的情況,那麼如果有多個構造方法呢?

又分爲兩種情況,多個構造方法中存不存在無參的構造方法。

分析:一個類存在多個構造方法,那麼Spring進行實例化之前,該如何去確定到底用哪個構造方法呢?

  1. 如果開發者指定了想要使用的構造方法,那麼就用這個構造方法
  2. 如果開發者沒有指定想要使用的構造方法,則看開發者有沒有讓Spring自動去選擇構造方法
  3. 如果開發者也沒有讓Spring自動去選擇構造方法,則Spring利用無參構造方法,如果沒有無參構造方法,則報錯

針對第一點,開發者可以通過什麼方式來指定使用哪個構造方法呢?

  1. xml中的<constructor-arg>標籤,這個標籤表示構造方法參數,所以可以根據這個確定想要使用的構造方法的參數個數,從而確定想要使用的構造方法
  2. 通過@Autowired註解,@Autowired註解可以寫在構造方法上,所以哪個構造方法上寫了@Autowired註解,表示開發者想使用哪個構造方法,當然,它和第一個方式的不同點是,通過xml的方式,我們直接指定了構造方法的參數值,而通過@Autowired註解的方式,需要Spring通過byType+byName的方式去找到符合條件的bean作爲構造方法的參數值

再來看第二點,如果開發者沒有指定想要使用的構造方法,則看開發者有沒有讓Spring自動去選擇構造方法,對於這一點,只能用在ClassPathXmlApplicationContext,因爲通過AnnotationConfigApplicationContext沒有辦法去指定某個bean可以自動去選擇構造方法,而通過ClassPathXmlApplicationContext可以在xml中指定某個bean的autowire爲constructor,雖然這個屬性表示通過構造方法自動注入,所以需要自動的去選擇一個構造方法進行自動注入,因爲是構造方法,所以順便是進行實例化。

當然,還有一種情況,就是多個構造方法上寫了@Autowired註解,那麼此時Spring會報錯。

但是,因爲@Autowired還有一個屬性required,默認爲ture,所以一個類中,只有能一個構造方法標註了@Autowired或@Autowired(required=true),有多個會報錯。但是可以有多個@Autowired(required=false),這種情況下,需要Spring從這些構造方法中去自動選擇一個構造方法。

總結:

1、 默認情況,用無參構造方法,或者只有一個構造方法就用那一個

2、 程序員指定了構造方法入參值,通過getBean()或者BeanDefinition.getConstructorArgumentValues()指定,那就用所匹配的構造方法

3、 程序員想讓Spring自動選擇構造方法以及構造方法的入參值, autowire="constructor"

4、 程序員通過@Autowired註解指定了某個構造方法,但是希望Spring自動找該構造方法的入參值

源碼思路

  1. AbstractAutowireCapableBeanFactory類中的createBeanInstance()方法會去創建一個Bean實例
  2. 根據BeanDefinition加載類得到Class對象
  3. 如果BeanDefinition綁定了一個Supplier,那就調用Supplier的get方法得到一個對象並直接返回
  4. 如果BeanDefinition中存在factoryMethodName,那麼就調用該工廠方法得到一個bean對象並返回
  5. 如果BeanDefinition已經自動構造過了,那就調用autowireConstructor()自動構造一個對象
  6. 調用SmartInstantiationAwareBeanPostProcessor的determineCandidateConstructors()方法得到哪些構造方法是可以用的
  7. 如果存在可用得構造方法,或者當前BeanDefinition的autowired是AUTOWIRE_CONSTRUCTOR,或者BeanDefinition中指定了構造方法參數值,或者創建Bean的時候指定了構造方法參數值,那麼就調用autowireConstructor()方法自動構造一個對象
  8. 最後,如果不是上述情況,就根據無參的構造方法實例化一個對象

autowireConstructor()

  1. 先檢查是否指定了具體的構造方法和構造方法參數值,或者在BeanDefinition中緩存了具體的構造方法或構造方法參數值,如果存在那麼則直接使用該構造方法進行實例化
  2. 如果沒有確定的構造方法或構造方法參數值,那麼
    1. 如果沒有確定的構造方法,那麼則找出類中所有的構造方法
    2. 如果只有一個無參的構造方法,那麼直接使用無參的構造方法進行實例化
    3. 如果有多個可用的構造方法或者當前Bean需要自動通過構造方法注入
    4. 根據所指定的構造方法參數值,確定所需要的最少的構造方法參數值的個數
    5. 對所有的構造方法進行排序,參數個數多的在前面
    6. 遍歷每個構造方法
    7. 如果不是調用getBean方法時所指定的構造方法參數值,那麼則根據構造方法參數類型找值
    8. 如果時調用getBean方法時所指定的構造方法參數值,就直接利用這些值
    9. 如果根據當前構造方法找到了對應的構造方法參數值,那麼這個構造方法就是可用的,但是不一定這個構造方法就是最佳的,所以這裏會涉及到是否有多個構造方法匹配了同樣的值,這個時候就會用值和構造方法類型進行匹配程度的打分,找到一個最匹配的

爲什麼分越少優先級越高?

主要是計算找到的bean和構造方法參數類型匹配程度有多高。

假設bean的類型爲A,A的父類是B,B的父類是C,同時A實現了接口D

如果構造方法的參數類型爲A,那麼完全匹配,得分爲0

如果構造方法的參數類型爲B,那麼得分爲2

如果構造方法的參數類型爲C,那麼得分爲4

如果構造方法的參數類型爲D,那麼得分爲1

可以直接使用如下代碼進行測試:

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));

所以,我們可以發現,越匹配分數越低。

@Bean的情況

首先,Spring會把@Bean修飾的方法解析成BeanDefinition:

  1. 如果方法不是static的,那麼解析出來的BeanDefinition中:
    1. factoryBeanName爲AppConfig所對應的beanName,比如"appConfig"
    2. factoryMethodName爲對應的方法名,比如"aService"
    3. factoryClass爲AppConfig.class
  1. 如果方法是static的,那麼解析出來的BeanDefinition中:
    1. factoryBeanName爲null
    2. factoryMethodName爲對應的方法名,比如"aService"
    3. factoryClass也爲AppConfig.class

在由@Bean生成的BeanDefinition中,有一個重要的屬性isFactoryMethodUnique,表示factoryMethod是不是唯一的,在普通情況下@Bean生成的BeanDefinition的isFactoryMethodUnique爲true,但是如果出現了方法重載,那麼就是特殊的情況,比如:

	@Bean
	public static AService aService(){
		return new AService();
	}

	@Bean
	public AService aService(BService bService){
		return new AService();
	}

雖然有兩個@Bean,但是肯定只會生成一個aService的Bean,那麼Spring在處理@Bean時,也只會生成一個aService的BeanDefinition,比如Spring先解析到第一個@Bean,會生成一個BeanDefinition,此時isFactoryMethodUnique爲true,但是解析到第二個@Bean時,會判斷出來beanDefinitionMap中已經存在一個aService的BeanDefinition了,那麼會把之前的這個BeanDefinition的isFactoryMethodUnique修改爲false,並且不會生成新的BeanDefinition了。

並且後續在根據BeanDefinition創建Bean時,會根據isFactoryMethodUnique來操作,如果爲true,那就表示當前BeanDefinition只對應了一個方法,那也就是隻能用這個方法來創建Bean了,但是如果isFactoryMethodUnique爲false,那就表示當前BeanDefition對應了多個方法,需要和推斷構造方法的邏輯一樣,去選擇用哪個方法來創建Bean。

 

本系列文章來自圖靈學院周瑜老師分享,本博客整理學習並搬運

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