@Autowired注解是spring用來支持依賴注入的核心利器之一,但是我們或多或少都會遇到required a single bean, but 2 were found(2可能是其他數字)的問題,接下來我們從原始碼的角度去看為什么會出現這個問題,以及這個問題的解法是什么?
首先我們寫一個demo來復現一下這個問題,首先我們有一個抽象類AbstractAutowiredDemo,兩個實作類AutowiredDemo1,AutowiredDemo2,然后我們在AutowiredDemoController中通過@Autowired依賴注入AbstractAutowiredDemo,
@RestController
public class AutowiredDemoController {
@Autowired
private AbstractAutowiredDemo abstractAutowiredDemo;
}
@Component
public abstract class AbstractAutowiredDemo {
public abstract String print();
}
@Component
public class AutowiredDemo2 extends AbstractAutowiredDemo {
@Override
public String print() {
return "AutowiredDemo2";
}
}
@Component
public class AutowiredDemo1 extends AbstractAutowiredDemo {
@Override
public String print() {
return "AutowiredDemo1";
}
}
此時我們啟動專案就會出現如下報錯,找到了兩個,并且列出了找到的兩個其實就是抽象類的實作類,

接下來,我們從原始碼的角度來看看,spring是如何查找依賴并注入的,
與之前查看@Component注解方法一致,我們全域搜索Autowired,會找到一個叫做AutowiredAnnotationBeanPostProcessor,根據命名AutowiredAnnotationXXX我們可以大概知道這個類是用來處理注解@Autowired的,

進入AutowiredAnnotationBeanPostProcessor,從注釋上我們可以知道這個類可以處理注解@Autowired,@Value以及如果支持的話還有@Inject,這里我們就只用關注@Autowired就行了,其他的以后再看,并且在無參構造器中有設定支持這些型別,

然后開始進入正題,我們開始真正去看,spring是如何處理,如何查找依賴并注入的,但是我們的主線任務是為什么會出現上面的錯誤,這樣有目的的看,先拋開其他細節,要相對容易一些,
這里可以是一個看源代碼的技巧,之前的ComponentScanAnnotationParser很簡單,里面只有一個parse方法,我們知道就看它,但是在AutowiredAnnotationBeanPostProcessor這個里面,這么多方法,我們應該看什么呢?首先我們要的是處理注解的方法,應該是提供出去的方法,所以應該是個pubilic方法,(我們平時編碼的時候也應該是這個習慣,往外提供的public方法應該放在前面,protect,peivate這種往后面放,因為作用域越小通用性越低,用到的概率越小),而前面的幾個public方法都是在set屬性值,所以排除掉,然后跟著兩個看命名是跟bean定義有關的,一個是合并,一個是重置,可以暫時排出掉,然后跟著是個決定使用哪個構造器的,應該是找到bean然后實體化時候用的,接下來就是一個后置處理屬性的,而我們的@Autowired就是注解在屬性欄位上,這里我們多看一步,看看方法的實作,有Injection of autowired dependencies字樣,并且根據命名有先查元資料,再注入的程序,猜測是這個方法,

接下來著重看AutowiredAnnotationBeanPostProcessor.postProcessProperties這個方法,
首先第一行,看方法名是在查找要注入的元資料,
進入方法AutowiredAnnotationBeanPostProcessor.findAutowiringMetadata,我們可以看到這段代碼是先判斷cache中是否已經有了,并且是否需要重繪(重繪其實就是為慷訓者型別不是clazz,可以自行點進去查看),不需要直接回傳,需要就開始加鎖(加鎖之后又進行了一次校驗,雙重校驗,小知識點,避免在加鎖的程序中,已經put進去),再進行構建元資料buildAutowiringMetadata

進入方法AutowiredAnnotationBeanPostProcessor.buildAutowiringMetadata,看第一個判斷,記不記得剛開始講的,這個類可以處理的注解型別,這里就在判斷,我們的@Autowired是肯定在其中的,然后中間又個do...while回圈,將當前類以及它的父類被注解了欄位,方法放入elements中,最侄訓傳一個InjectionMetadata物件,并且設定了它的targetClass為clazz,injectedElements為elements,現在相當于我們需要進行依賴注入的元資料找到了,

接下來開始注入程序,我們回到postProcessProperties方法,查看注入方法,
進入InjectionMetadata.inject方法,我們在上面找到的元資料這里就用到的,我們不管checkedElements,至少我們的injectedElements肯定是有的,在上一步查找元資料的時候,我們已經set進去了,接下來我們就繼續往下走,

當我們繼續進入inject方法的時候,我們發現注釋上有一句話,this和getResourceToInject都需要覆寫這個方法,所以這個方法并不是我們需要的注入方法的實作,

點擊左邊向下箭頭,我們可以發現兩個實作方法,根據命名,一個處理field的,一個處理method的,顯然我們這里需要的是處理field的,

進入AutowiredFieldElement.inject方法,我們看到他先判斷了是否有快取,我們這里假設就是第一次,沒有快取(快取肯定也是之前加載進去的),這樣我們就應該走的是else分支,

進入AutowiredFieldElement.inject.resolveFieldValue方法,我們可以看到,開頭是在做一些準備作業,可以忽略,最后是在將查找到的快取起來,我們也可以不看,重點就是try中的內容,解決依賴,

進入方法AutowireCapableBeanFactory.resolveDependency,我們需要找它的實作方法,點擊左邊向下箭頭,可以看到兩個實作方法,同樣根據命名,紅框內的很顯然是用來處理bean的,

進入DefaultListableBeanFactory.resolveDependency方法,大概掃一眼,前面都是在判斷descriptor.getDependencyType()這個的值是不是那些類的型別,很顯然是我們自己定義的類,都不是這些型別,所以我們直接到最后一個else,else中第一句是如果是懶加載,就先不加載了,所以真正的邏輯在下面,(其實我們就是要找到解決依賴的方法,而spring方法命名都是見文知意的,所以我們可以先直接定位到下面,發現不對再說,這是看原始碼時候的一個思路)

進入DefaultListableBeanFactory.doResolveDependency方法,這里就是真正的查找依賴的核心了,接下來我們仔細分析一下,

Step1:通過descriptor.resolveShortcut(this)回傳shortcut,我們點進這個方法查看注釋可以發現,這是用來做一些預先決議的,一般是spring自用的,我們如果沒有特殊設定,一般不會用到,所以這個shortcut應該為null,方法不會回傳,
Step2:通過getAutowireCandidateResolver().getSuggestedValue(descriptor)回傳value,點進方法查看,根據注釋看,這個是給給定依賴建議默認值的,應該處理的是@Value,所以這里value為null,方法不回傳,
Step3:通過resolveMultipleBeans回傳multipleBeans,可以看到里面是在判斷我們當前查找的依賴的型別是否符合哪些條件(stream或者集合型別,所以這個叫multi),而我們當前的type就是我們定義的抽象類,所以這里multipleBeans也為null,方法不回傳,
Step4:通過findAutowireCandidates回傳matchingBeans(其實看這個方法名,就是處理Autowired注解,查找候選者的),點進方法查看,
進入方法DefaultListableBeanFactory.findAutowireCandidates,首先第一行我們可以看到在查找候選者名稱,

進入方法BeanFactoryUtils.beanNamesForTypeIncludingAncestors,我們可以看到這里又呼叫了一個方法,通過type獲取beanNames,點進去看注釋可以看到這里會獲取當前型別的bean的名稱(會排除抽象類,不再深入進去,可以自己點進去看),包括子類,其實看到這里應該大概猜出來了,我們通過上面的抽象類AbstractAutowiredDemo拿到了它的子類,所以報錯里面出現的是子類AutowiredDemo1和AutowiredDemo2,接著中間一段可能會查出更多的,但是這里我們不關心了,現在我們直接回傳,此時String[]應該包含兩個元素,

回到方法DefaultListableBeanFactory.findAutowireCandidates,我們可以發現,result中至少有兩個元素,下面的for都是在里面繼續add,這里我們不再看,繼續往外走,

回到方法DefaultListableBeanFactory.doResolveDependency,matchingBeans中至少會有兩個元素,則會進入下面一個if,而在if里面第一個代碼就是在決定到底選用哪個候選bean,這里也是我們解決這個問題的一個切入點,

進入DefaultListableBeanFactory.determineAutowireCandidate會發現它先找了是否設定primary,priority,都沒有的話就回圈,查看有沒有已經加載了的或者就是當前,這個最終目的就是要決定一個候選作為依賴注入,但是我們的這個案例,很顯然決定不了,

回到外面方法之后,因為@Aurowired的required默認就是為true,所以一定會進入這個if,回傳一個找到不唯一的例外,

總結
@Autowired注解欄位查找并注入依賴的程序可以概括為:找到需要依賴注入的欄位,通過class型別查找可以注入的類(包括子類),決定注入類,注入,
所以要解決文章開始出現的問題,有兩個辦法:
1.在查找處規避,注入的時候指定是Demo1還是Demo2

2.在決定注入類處規避,通過注解@Primary或者@Priority

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/501335.html
標籤:其他
