javascript
对不起,我就是喜欢问你Spring构造器注入原理
點(diǎn)擊上方?好好學(xué)java?,選擇?星標(biāo)?公眾號(hào)
前言
Spring IOC是面試常問(wèn)的知識(shí)點(diǎn)。本文講述了從自定義注冊(cè)Bean開(kāi)始,到解析IOC容器初始化Bean的判斷的一系列過(guò)程,從現(xiàn)象看本質(zhì),分析了Spring中的構(gòu)造器注入的原理,并且分析了各種情況,相信理解了的讀者將來(lái)遇到這類(lèi)的別的問(wèn)題可以獨(dú)立思考出答案。
1. 示例
先來(lái)看一個(gè)例子,看看什么是構(gòu)造器注入。
這里我寫(xiě)了一個(gè)類(lèi),分別有三個(gè)構(gòu)造器,一個(gè)是注入一個(gè)Bean的構(gòu)造器,一個(gè)是注入兩個(gè)Bean的構(gòu)造器,還有一個(gè)無(wú)參構(gòu)造器:
Model類(lèi)User與Role我就不貼代碼了,分別是有兩個(gè)變量,一個(gè)id,一個(gè)name。
然后就是Spring的配置文件context.xml:
注意,如果需要使用構(gòu)造器注入,需要 <context:annotation-config /> 此自定義標(biāo)簽開(kāi)啟(關(guān)于自定義標(biāo)簽,在本人往期的Spring系列中有詳細(xì)介紹),具體作用后面再作分析。
那么,該類(lèi)三個(gè)構(gòu)造器,Spring會(huì)使用哪個(gè)構(gòu)造器初始化ConstructorAutowiredTest這個(gè)Bean呢?寫(xiě)個(gè)測(cè)試便知:
執(zhí)行test方法,控制臺(tái)打印:
從這里我們可以看出來(lái),此時(shí)三個(gè)構(gòu)造器中Spring使用了無(wú)參構(gòu)造器。
我們換一個(gè)方式,將無(wú)參構(gòu)造器注釋掉,看看Spring到底會(huì)使用哪個(gè)構(gòu)造器呢?同樣執(zhí)行test方法測(cè)試,控制臺(tái)打印:
此時(shí)控制臺(tái)報(bào)錯(cuò),大致意思是Bean的實(shí)例化失敗了,沒(méi)有無(wú)參的構(gòu)造器方法調(diào)用。此時(shí)有個(gè)疑問(wèn),明明構(gòu)造器中的參數(shù)都是Bean,為什么不能初始化,一定要使用無(wú)參的構(gòu)造器呢?是否是因?yàn)橛袃蓚€(gè)構(gòu)造器的原因?此時(shí)我們?cè)僮⑨尩羧我庖粋€(gè)構(gòu)造函數(shù),使測(cè)試類(lèi)只有一個(gè)帶參構(gòu)造函數(shù):
再次運(yùn)行測(cè)試類(lèi),控制臺(tái)打印:
如果是注釋掉第二個(gè)構(gòu)造函數(shù),則結(jié)果是兩個(gè)對(duì)象都有。從這里我們可以看出,如果只有一個(gè)構(gòu)造器,且參數(shù)為IOC容器中的Bean,將會(huì)執(zhí)行自動(dòng)注入。這里又有一個(gè)疑問(wèn),這也太不科學(xué)了吧,強(qiáng)制用戶一定只能寫(xiě)一個(gè)構(gòu)造器?這時(shí)我們猜想@Autowired注解是否能解決這種問(wèn)題?來(lái)試試吧。我們將構(gòu)造器全部解除注釋,將第三個(gè)構(gòu)造器打上@Autowired注解:
運(yùn)行測(cè)試,控制臺(tái)打印:
不出所料,@Autowired注解可以解決這種問(wèn)題,此時(shí)Spring將使用有注解的構(gòu)造函數(shù)進(jìn)行Bean的初始化。那么,如果有兩個(gè)@Autowired注解呢?結(jié)果肯定是報(bào)錯(cuò),因?yàn)?#64;Autowired的默認(rèn)屬性required是為true的,也就是說(shuō)兩個(gè)required=true的構(gòu)造器,Spring不知道使用哪一個(gè)。但如果是這樣寫(xiě)的話:
結(jié)果是怎樣的呢?看看控制臺(tái)打印:
使用參數(shù)最多的那一個(gè)構(gòu)造器來(lái)初始化Bean。又如果兩個(gè)有參構(gòu)造器順序調(diào)換又是怎樣的呢?一個(gè)required為false一個(gè)為true,結(jié)果又是怎樣的呢?這里直接給出答案,順序調(diào)換依然使用多參數(shù)構(gòu)造器,并且required只要有一個(gè)true就會(huì)報(bào)錯(cuò)。有興趣的讀者可以自己試試,下面將深入源碼分析構(gòu)造器注入的過(guò)程,相信上述所有疑問(wèn)都能得到解答。
疑問(wèn)點(diǎn)小結(jié)
從現(xiàn)象看本質(zhì),我們從上面的例子中,大致可以得到以下幾個(gè)疑問(wèn):
為什么寫(xiě)三個(gè)構(gòu)造器(含有無(wú)參構(gòu)造器),并且沒(méi)有@Autowired注解,Spring總是使用無(wú)參構(gòu)造器實(shí)例化Bean?
為什么注釋掉兩個(gè)構(gòu)造器,留下一個(gè)有參構(gòu)造器,并且沒(méi)有@Autowired注解,Spring將會(huì)使用構(gòu)造器注入Bean的方式初始化Bean?
為什么寫(xiě)三個(gè)構(gòu)造器,并且在其中一個(gè)構(gòu)造器上打上**@Autowired注解,就可以正常注入構(gòu)造器?并且兩個(gè)@Autowired注解就會(huì)報(bào)錯(cuò)**,一定需要在所有@Autowired中的required都加上false即可正常初始化等等?
或許有一些沒(méi)有提到的疑問(wèn)點(diǎn),但大致就這么多吧,舉多了也沒(méi)用,因?yàn)樵谙旅嫔钊朐创a的分析中讀者若是可以理解,關(guān)于此類(lèi)的一系列問(wèn)題都將可以自己思考得出結(jié)果,得到可以舉一反三的能力。
2. 依賴注入伊始
在開(kāi)頭,我們有提到,如果需要構(gòu)造器注入的功能的話,我們需要在xml配置中寫(xiě)下這樣一段代碼:
如果有看過(guò)本人自定義標(biāo)簽或是有自定義標(biāo)簽的基礎(chǔ)的讀者,第一反應(yīng)應(yīng)該是先看自定義標(biāo)簽的context對(duì)應(yīng)的命名空間是哪個(gè):
我們?nèi)炙阉鞔嗣臻g(http后需要加一個(gè)斜桿符號(hào)\)得到一個(gè)spring.handlers文件:
http\://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler這里說(shuō)明了此命名空間對(duì)應(yīng)的NamespaceHandler,進(jìn)入此類(lèi)看看其init方法:
我們的自定義標(biāo)簽是叫作?“annotation-config"?,所以對(duì)應(yīng)的解析器是AnnotationConfigBeanDefinitionParser這個(gè)類(lèi),進(jìn)入這個(gè)類(lèi)的parse方法:
這里只需要關(guān)注registerAnnotationConfigProcessors方法,看看到底需要注冊(cè)哪些Bean:
到這里我們可以知道,此自定義標(biāo)簽注冊(cè)了一個(gè)AutowiredAnnotationBeanPostProcessor類(lèi)的Bean到IOC容器。那么此類(lèi)是干什么用的呢?
3. 初始化Bean
我們將思路轉(zhuǎn)到IOC容器初始化Bean的流程中來(lái),在getBean方法獲取一個(gè)Bean的時(shí)候,IOC容器才開(kāi)始將Bean進(jìn)行初始化,此時(shí)會(huì)先實(shí)例化一個(gè)Bean,然后再對(duì)Bean進(jìn)行依賴注入,然后執(zhí)行一系列初始化的方法,完成Bean的整個(gè)初始化過(guò)程。而本文討論的構(gòu)造器注入,則是在實(shí)例化Bean的過(guò)程中進(jìn)行的。在AbstractAutowireCapableBeanFactory類(lèi)中的doCreateBean方法獲取Bean的開(kāi)始,將調(diào)用createBeanInstance方法進(jìn)行Bean的實(shí)例化(選擇Bean使用哪個(gè)構(gòu)造器實(shí)例化):
到這里我們可以知道,determineConstructorsFromBeanPostProcessors方法將選擇是否有適合的自動(dòng)注入構(gòu)造器,如果沒(méi)有,將使用無(wú)參構(gòu)造器實(shí)例化,關(guān)鍵就在這個(gè)方法中,是如何判斷哪些構(gòu)造器使用自動(dòng)注入的呢:
這段代碼告訴我們,這里會(huì)使用SmartInstantiationAwareBeanPostProcessor類(lèi)型的BeanPostProcessor進(jìn)行判斷,我們回顧一下上面的依賴注入伊始的時(shí)候我們說(shuō)的自定義標(biāo)簽注冊(cè)的類(lèi)的結(jié)構(gòu):
有看過(guò)本人關(guān)于Sprng擴(kuò)展篇的文章或是有Spring擴(kuò)展點(diǎn)基礎(chǔ)的讀者,應(yīng)該可以知道,若是注冊(cè)一個(gè)BeanPostProcessor到IOC容器中,在AbstractApplicationContext中的refresh方法會(huì)對(duì)這些類(lèi)型的Bean進(jìn)行處理,存放在一個(gè)集合,此時(shí)getBeanPostProcessors方法就可以獲取到所有BeanPostProcessor集合,遍歷集合,便可以調(diào)用到我們自定義標(biāo)簽中注冊(cè)的這個(gè)類(lèi)型的Bean。當(dāng)然,SmartInstantiationAwareBeanPostProcessor類(lèi)型的Bean有很多,但依賴注入是使用上述這個(gè)Bean來(lái)完成的。回到主線,下面會(huì)調(diào)用它的determineCandidateConstructors方法進(jìn)行查找對(duì)應(yīng)構(gòu)造器(核心方法):
public Constructor<?>[] determineCandidateConstructors(Class<?> beanClass, final String beanName)throws BeanCreationException {//略..// Quick check on the concurrent map first, with minimal locking.//先查找緩存Constructor<?>[] candidateConstructors = this.candidateConstructorsCache.get(beanClass);//若是緩存中沒(méi)有if (candidateConstructors == null) {// Fully synchronized resolution now...//同步此方法synchronized (this.candidateConstructorsCache) {candidateConstructors = this.candidateConstructorsCache.get(beanClass);//雙重判斷,避免多線程并發(fā)問(wèn)題if (candidateConstructors == null) {Constructor<?>[] rawCandidates;try {//獲取此Bean的所有構(gòu)造器rawCandidates = beanClass.getDeclaredConstructors();}catch (Throwable ex) {throw new BeanCreationException(beanName,"Resolution of declared constructors on bean Class [" + beanClass.getName() +"] from ClassLoader [" + beanClass.getClassLoader() + "] failed", ex);}//最終適用的構(gòu)造器集合List<Constructor<?>> candidates = new ArrayList<>(rawCandidates.length);//存放依賴注入的required=true的構(gòu)造器Constructor<?> requiredConstructor = null;//存放默認(rèn)構(gòu)造器Constructor<?> defaultConstructor = null;Constructor<?> primaryConstructor = BeanUtils.findPrimaryConstructor(beanClass);int nonSyntheticConstructors = 0;for (Constructor<?> candidate : rawCandidates) {if (!candidate.isSynthetic()) {nonSyntheticConstructors++;}else if (primaryConstructor != null) {continue;}//查找當(dāng)前構(gòu)造器上的注解AnnotationAttributes ann = findAutowiredAnnotation(candidate);//若沒(méi)有注解if (ann == null) {Class<?> userClass = ClassUtils.getUserClass(beanClass);if (userClass != beanClass) {try {Constructor<?> superCtor =userClass.getDeclaredConstructor(candidate.getParameterTypes());ann = findAutowiredAnnotation(superCtor);}catch (NoSuchMethodException ex) {// Simply proceed, no equivalent superclass constructor found...}}}//若有注解if (ann != null) {//已經(jīng)存在一個(gè)required=true的構(gòu)造器了,拋出異常if (requiredConstructor != null) {throw new BeanCreationException(beanName,"Invalid autowire-marked constructor: " + candidate +". Found constructor with 'required' Autowired annotation already: " +requiredConstructor);}//判斷此注解上的required屬性boolean required = determineRequiredStatus(ann);//若為trueif (required) {if (!candidates.isEmpty()) {throw new BeanCreationException(beanName,"Invalid autowire-marked constructors: " + candidates +". Found constructor with 'required' Autowired annotation: " +candidate);}//將當(dāng)前構(gòu)造器加入requiredConstructor集合requiredConstructor = candidate;}//加入適用的構(gòu)造器集合中candidates.add(candidate);}//如果該構(gòu)造函數(shù)上沒(méi)有注解,再判斷構(gòu)造函數(shù)上的參數(shù)個(gè)數(shù)是否為0else if (candidate.getParameterCount() == 0) {//如果沒(méi)有參數(shù),加入defaultConstructor集合defaultConstructor = candidate;}}//適用的構(gòu)造器集合若不為空if (!candidates.isEmpty()) {// Add default constructor to list of optional constructors, as fallback.//若沒(méi)有required=true的構(gòu)造器if (requiredConstructor == null) {if (defaultConstructor != null) {//將defaultConstructor集合的構(gòu)造器加入適用構(gòu)造器集合candidates.add(defaultConstructor);}else if (candidates.size() == 1 && logger.isInfoEnabled()) {logger.info("Inconsistent constructor declaration on bean with name '" + beanName +"': single autowire-marked constructor flagged as optional - " +"this constructor is effectively required since there is no " +"default constructor to fall back to: " + candidates.get(0));}}//將適用構(gòu)造器集合賦值給將要返回的構(gòu)造器集合candidateConstructors = candidates.toArray(new Constructor<?>[0]);}//如果適用的構(gòu)造器集合為空,且Bean只有一個(gè)構(gòu)造器并且此構(gòu)造器參數(shù)數(shù)量大于0else if (rawCandidates.length == 1 && rawCandidates[0].getParameterCount() > 0) {//就使用此構(gòu)造器來(lái)初始化candidateConstructors = new Constructor<?>[] {rawCandidates[0]};}//如果構(gòu)造器有兩個(gè),且默認(rèn)構(gòu)造器不為空else if (nonSyntheticConstructors == 2 && primaryConstructor != null &&defaultConstructor != null && !primaryConstructor.equals(defaultConstructor)) {//使用默認(rèn)構(gòu)造器返回candidateConstructors = new Constructor<?>[] {primaryConstructor, defaultConstructor};}else if (nonSyntheticConstructors == 1 && primaryConstructor != null) {candidateConstructors = new Constructor<?>[] {primaryConstructor};}else {//上述都不符合的話,只能返回一個(gè)空集合了candidateConstructors = new Constructor<?>[0];}//放入緩存,方便下一次調(diào)用this.candidateConstructorsCache.put(beanClass, candidateConstructors);}}}//返回candidateConstructors集合,若為空集合返回nullreturn (candidateConstructors.length > 0 ? candidateConstructors : null); }從這段核心代碼我們可以看出幾個(gè)要點(diǎn):
在沒(méi)有@Autowired注解的情況下:
無(wú)參構(gòu)造器將直接加入defaultConstructor集合中。
在構(gòu)造器數(shù)量只有一個(gè)且有參數(shù)時(shí),此唯一有參構(gòu)造器將加入candidateConstructors集合中。
在構(gòu)造器數(shù)量有兩個(gè)的時(shí)候,并且存在無(wú)參構(gòu)造器,將defaultConstructor(第一條的無(wú)參構(gòu)造器)放入candidateConstructors集合中。
在構(gòu)造器數(shù)量大于兩個(gè),并且存在無(wú)參構(gòu)造器的情況下,將返回一個(gè)空的candidateConstructors集合,也就是沒(méi)有找到構(gòu)造器。
在有@Autowired注解的情況下:
判斷required屬性:
true:先判斷requiredConstructor集合是否為空,若不為空則代表之前已經(jīng)有一個(gè)required=true的構(gòu)造器了,兩個(gè)true將拋出異常,再判斷candidates集合是否為空,若不為空則表示之前已經(jīng)有一個(gè)打了注解的構(gòu)造器,此時(shí)required又是true,拋出異常。若兩者都不為空將放入requiredConstructor集合中,再放入candidates集合中。
false:直接放入candidates集合中。
判斷requiredConstructor集合是否為空(是否存在required=true的構(gòu)造器),若沒(méi)有,將默認(rèn)構(gòu)造器也放入candidates集合中。
最后將上述candidates賦值給最終返回的candidateConstructors集合。
4.總結(jié)
綜上所述,我們可以回答開(kāi)篇疑問(wèn)點(diǎn)小結(jié)所總結(jié)的一系列問(wèn)題了:
為什么寫(xiě)三個(gè)構(gòu)造器(含有無(wú)參構(gòu)造器),并且沒(méi)有@Autowired注解,Spring總是使用無(wú)參構(gòu)造器實(shí)例化Bean?
答:參照沒(méi)有注解的處理方式:若構(gòu)造器只有兩個(gè),且存在無(wú)參構(gòu)造器,將直接使用無(wú)參構(gòu)造器初始化。若大于兩個(gè)構(gòu)造器,將返回一個(gè)空集合,也就是沒(méi)有找到合適的構(gòu)造器,那么參照第三節(jié)初始化Bean的第一段代碼createBeanInstance方法的末尾,將會(huì)使用無(wú)參構(gòu)造器進(jìn)行實(shí)例化。這也就解答了為什么沒(méi)有注解,Spring總是會(huì)使用無(wú)參的構(gòu)造器進(jìn)行實(shí)例化Bean,并且此時(shí)若沒(méi)有無(wú)參構(gòu)造器會(huì)拋出異常,實(shí)例化Bean失敗。
為什么注釋掉兩個(gè)構(gòu)造器,留下一個(gè)有參構(gòu)造器,并且沒(méi)有@Autowired注解,Spring將會(huì)使用構(gòu)造器注入Bean的方式初始化Bean?
答:參照沒(méi)有注解的處理方式:構(gòu)造器只有一個(gè)且有參數(shù)時(shí),將會(huì)把此構(gòu)造器作為適用的構(gòu)造器返回出去,使用此構(gòu)造器進(jìn)行實(shí)例化,參數(shù)自然會(huì)從IOC中獲取Bean進(jìn)行注入。
為什么寫(xiě)三個(gè)構(gòu)造器,并且在其中一個(gè)構(gòu)造器上打上@Autowired注解,就可以正常注入構(gòu)造器?
答:參照有注解的處理方式:在最后判斷candidates適用的構(gòu)造器集合是否為空時(shí),若有注解,此集合當(dāng)然不為空,且required=true,也不會(huì)將默認(rèn)構(gòu)造器集合defaultConstructor加入candidates集合中,最終返回的是candidates集合的數(shù)據(jù),也就是這唯一一個(gè)打了注解的構(gòu)造器,所以最終使用此打了注解的構(gòu)造器進(jìn)行實(shí)例化。
兩個(gè)@Autowired注解就會(huì)報(bào)錯(cuò),一定需要在所有@Autowired中的required都加上false即可正常初始化?
答:參照有注解的處理方式:當(dāng)打了兩個(gè)@Autowired注解,也就是兩個(gè)required都為true,將會(huì)拋出異常,若是一個(gè)為true,一個(gè)為false,也將會(huì)拋出異常,無(wú)論順序,因?yàn)橛袃蓪拥呐袛?#xff0c;一個(gè)是requiredConstructor集合是否為空的判斷,一個(gè)是candidates集合為空的判斷,若兩個(gè)構(gòu)造器的required屬性都為false,不會(huì)進(jìn)行上述判斷,直接放入candidates集合中,并且在下面的判斷中會(huì)將defaultConstructor加入到candidates集合中,也就是candidates集合有三個(gè)構(gòu)造器,作為結(jié)果返回。
至于第四條結(jié)論,返回的構(gòu)造器若有三個(gè),Spring將如何判斷使用哪一個(gè)構(gòu)造器呢?在后面Spring會(huì)遍歷三個(gè)構(gòu)造器,依次判斷參數(shù)是否是Spring的Bean(是否被IOC容器管理),若參數(shù)不是Bean,將跳過(guò)判斷下一個(gè)構(gòu)造器,也就是說(shuō),例如上述兩個(gè)參數(shù)的構(gòu)造器其中一個(gè)參數(shù)不是Bean,將判斷一個(gè)參數(shù)的構(gòu)造器,若此參數(shù)是Bean,使用一個(gè)參數(shù)的構(gòu)造器實(shí)例化,若此參數(shù)不是Bean,將使用無(wú)參構(gòu)造器實(shí)例化。也就是說(shuō),若使用@Autowired注解進(jìn)行構(gòu)造器注入,required屬性都設(shè)置為false的話,將避免無(wú)Bean注入的異常,使用無(wú)參構(gòu)造器正常實(shí)例化。若兩個(gè)參數(shù)都是Bean,則就直接使用兩個(gè)參數(shù)的構(gòu)造器進(jìn)行實(shí)例化并獲取對(duì)應(yīng)Bean注入構(gòu)造器。
在這里最后說(shuō)一點(diǎn),從上面可以看出,若想使用構(gòu)造器注入功能,最好將要注入的構(gòu)造器都打上@Autowired注解(若有多個(gè)需要注入的構(gòu)造器,將所有@Autowired中required屬性都設(shè)置為false),若有多個(gè)構(gòu)造器,只有一個(gè)構(gòu)造器需要注入,將這個(gè)構(gòu)造器打上@Autowired注解即可,不用設(shè)置required屬性。如果不打注解也是可以使用構(gòu)造器注入功能的,但構(gòu)造器數(shù)量只能為1,且代碼可讀性較差,讀代碼的人并不知道你這里使用了構(gòu)造器注入的方式,所以這里我建議若使用構(gòu)造器注入打上@Autowired注解會(huì)比較好一點(diǎn)。
推薦閱讀:
這樣配置:讓你的 IDEA 好用到飛起來(lái)
告訴你一種精簡(jiǎn)、優(yōu)化代碼的方式
分布式事務(wù)不理解?一次給你講清楚!
推薦???
關(guān)注查看:福利,獲取福利資源
Coding這件小事
點(diǎn)個(gè)“在看”再走吧,謝謝????
《新程序員》:云原生和全面數(shù)字化實(shí)踐50位技術(shù)專(zhuān)家共同創(chuàng)作,文字、視頻、音頻交互閱讀總結(jié)
以上是生活随笔為你收集整理的对不起,我就是喜欢问你Spring构造器注入原理的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 二本毕业,努力 5 年,月入 5w 的程
- 下一篇: 你以为大厂的代码就不烂?看看这几个公众号