摘要:?jiǎn)栴}今天小伙伴跑過(guò)來(lái)說(shuō),搭建框架的時(shí)候出現(xiàn)配置好的信息不能夠及時(shí)注入到實(shí)體類(lèi)中的情況。他通過(guò)實(shí)踐發(fā)現(xiàn),加載的時(shí)候,通過(guò)注入的實(shí)體類(lèi)里面沒(méi)有值。等到容器加載完成后,在層注入的是有數(shù)據(jù)的,搞了接近一天。表明當(dāng)前依賴(lài)于另外一個(gè),可以用來(lái)控制順序。
問(wèn)題
今天小伙伴跑過(guò)來(lái)說(shuō),搭建框架的時(shí)候出現(xiàn)disconf配置好的信息不能夠及時(shí)注入到實(shí)體類(lèi)中的情況。他通過(guò)實(shí)踐發(fā)現(xiàn),spring 加載Configuration 的時(shí)候,通過(guò)@Autowired注入的RedisProperties 實(shí)體類(lèi)里面沒(méi)有值。等到容器加載完成后,在Controller 層注入的RedisProperties是有數(shù)據(jù)的,搞了接近一天。我在他控制臺(tái)看到了如下信息(簡(jiǎn)化):
**** DISCONF START FIRST SCAN ****
//此處省略
**** DISCONF END FIRST SCAN ****
//@configuration 注冊(cè)bean的信息(可以自己添加日志)
**** DISCONF START SECOND SCAN ****
//此處省略
**** DISCONF END SECOND SCAN ****
通過(guò)信息可以看出,關(guān)鍵問(wèn)題出現(xiàn)在了第二次掃描在Bean注冊(cè)之后。第二次掃描負(fù)責(zé)將配置注入實(shí)體類(lèi)中,詳細(xì)可以參考disconf-client設(shè)計(jì)
那么第二次掃描在什么時(shí)候進(jìn)行的呢,打開(kāi)DisconfMgrBeanSecond 類(lèi)
public class DisconfMgrBeanSecond{ public void init(){ DisconfMgr.getInstance().secondScan(); //此處進(jìn)行第二次掃描 } public void destroy(){ DisconfMgr.getInstance().close(); } }
現(xiàn)在的問(wèn)題一下明了了,我們需要做的也就是將 DisconfMgrBeanSecond 的Bean注冊(cè)提前,提前至@Configuration之前。我這里用的是@DependsOn注解,將其放在Properties實(shí)體類(lèi)上。表明當(dāng)前Bean依賴(lài)于另外一個(gè)Bean,可以用來(lái)控制順序。
思考上面的方法只是使用技巧解決了實(shí)際問(wèn)題,我們不禁要思考了,spring加載的順序到底是怎么樣的?為什么有的項(xiàng)目沒(méi)有加載順序問(wèn)題,有的就會(huì)出bug。接下來(lái)我們就來(lái)深入擼一下spring的源碼。(本文基于的源碼為 spring boot 2.0.0.RELEASE)
調(diào)試方法很多人不太會(huì)調(diào)試源碼,一上手就從入口函數(shù)開(kāi)始,點(diǎn)幾下就自己犯暈了。還有些人習(xí)慣看類(lèi)圖,從全局去看,也會(huì)很累。這里不是說(shuō)類(lèi)圖方式不好,而是分情況而定。比如你讀 Java 集合框架,類(lèi)圖就是一個(gè)不錯(cuò)的選擇,一來(lái)集合類(lèi)功能相對(duì)獨(dú)立,二來(lái)集合本身很符合面向?qū)ο蟮乃枷?。面?duì)spring這種名字很相似,代碼龐大的大型框架時(shí),建議還是以點(diǎn)入面,有目的的去看。這里介紹一下我自己使用的方法:
編寫(xiě)測(cè)試工程,比如我要理解spring @Configuration的加載過(guò)程,先用spring boot 快速搭建一個(gè)可以運(yùn)行的工程
在自己需要了解的地方打斷點(diǎn)
觀察調(diào)用棧,找到關(guān)鍵方法
如下圖
Debugger 菜單欄中我們很容易找到調(diào)用棧的信息,觀察這些方法,我們可以看到這三個(gè)方法的方法名很像我們想知道的加載過(guò)程
在仔細(xì)點(diǎn)開(kāi)源碼會(huì)發(fā)現(xiàn) refresh()方法下的如下代碼
this.postProcessBeanFactory(beanFactory); //上下文子類(lèi)對(duì)beanFactory進(jìn)行后置處理 this.invokeBeanFactoryPostProcessors(beanFactory);//調(diào)用工廠處理器,對(duì)bean進(jìn)行注冊(cè) this.registerBeanPostProcessors(beanFactory); // 注冊(cè)bean的攔截處理器 this.initMessageSource(); //初始化消息源 this.initApplicationEventMulticaster(); //初始化上下文事件多播器 this.onRefresh(); //初始化其他子類(lèi)上下文的特殊beans this.registerListeners(); //檢查監(jiān)聽(tīng)類(lèi)的bean,并注冊(cè)他們 this.finishBeanFactoryInitialization(beanFactory); //實(shí)例化剩余非懶加載的bean單利 this.finishRefresh(); //完成后刷新,發(fā)布相應(yīng)的事件
如果你通過(guò)idea把源碼下載下來(lái)的話,可以看到光標(biāo)停在 this.finishBeanFactoryInitialization(beanFactory)處,表明此時(shí)具體進(jìn)入的方法。好了,調(diào)試方法暫時(shí)就說(shuō)到這里,還是來(lái)看源碼吧。
源碼分析上面提了一下@Configuration注解的bean 入口在finishBeanFactoryInitialization(beanFactory)方法中,接著往下走到preInstantiateSingletons()方法中
我們發(fā)現(xiàn)這個(gè)方法里有一個(gè)特別顯眼的屬性,beanDefinitionNames,這個(gè)就是容器的注冊(cè)順序。
我們端點(diǎn)是打在了Test類(lèi)初始化的地方,但通過(guò)debugger 可以發(fā)現(xiàn)入口方法加載的反而是TestController類(lèi),并且中間方法的調(diào)用并沒(méi)有出現(xiàn)HelloServiceimpl類(lèi)和TestServiceImpl類(lèi)的加載??梢?jiàn)真實(shí)bean初始化的順序并不是這樣的。
回頭去找 beanDefinitionNames在哪里初始化的,可以發(fā)現(xiàn)在registerBeanDefinition(String beanName, BeanDefinition beanDefinition)方法中,循環(huán)添加的,接下來(lái)再去找registerBeanDefinition 在什么地方調(diào)用。
再次打斷點(diǎn)定位到 ClassPathBeanDefinitionScanner.doscan() 方法上
protected SetdoScan(String... basePackages) { Assert.notEmpty(basePackages, "At least one base package must be specified"); Set beanDefinitions = new LinkedHashSet<>(); for (String basePackage : basePackages) { //掃描package,尋找候選組件 Set candidates = findCandidateComponents(basePackage); //候選組件進(jìn)行處理,處理其他注解 for (BeanDefinition candidate : candidates) { ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate); candidate.setScope(scopeMetadata.getScopeName()); String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry); if (candidate instanceof AbstractBeanDefinition) { postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName); } if (candidate instanceof AnnotatedBeanDefinition) { AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate); } if (checkCandidate(beanName, candidate)) { BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName); definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry); beanDefinitions.add(definitionHolder); registerBeanDefinition(definitionHolder, this.registry); } } } return beanDefinitions; }
首先通過(guò)掃描找出候選組件,掃描的范圍包含basePackages目錄下的所有class文件,如果符合條件,將其放在LinkedHashSet中,使其保證唯一有序。判斷條件在ClassPathScanningCandidateComponentProvider.isCandidateComponent()方法中。這個(gè)類(lèi)有兩個(gè)屬性,excludeFilters和includeFilters,分別控制著候選類(lèi)的排除鏈和包含鏈。我debugger不進(jìn)行設(shè)置的話,默認(rèn)選取下面三種接口子類(lèi)作為候選加載類(lèi),org.springframework.stereotype.Component,javax.annotation.ManagedBean,javax.inject.Named,而@Configuration,@Controller,@Service,@Repository,都是基于Component的注解。
真實(shí)bean的加載上面只是說(shuō)明白了類(lèi)文件的注冊(cè)順序,他是通過(guò)掃描包名,類(lèi)名這樣排下來(lái)的,只是一個(gè)初步順序。
先來(lái)看一下之前調(diào)試的初步順序 testConfig-->helloController-->testController-->helloServiceImpl-->testServiceImpl-->test
整體看下來(lái),他是按照包名和類(lèi)型排序的,只不過(guò)有一點(diǎn)需要注意 test 所在的包實(shí)際上是在Impl 前面的,且Test類(lèi)上沒(méi)有任何注解,這表明他們的注冊(cè)順序其實(shí)是:先掃描Component,在掃描@Bean注解。
當(dāng)bean真正加載的時(shí)候是這樣加載的,每加載一個(gè)類(lèi),看他有沒(méi)有依賴(lài),有的話同時(shí)加載依賴(lài)bean。這也就解釋了為什么testController為什么跳過(guò)impl 直接加載test。
如何控制加載順序其實(shí)有很多方法控制順序,依賴(lài)注入提前,@DepensOn 和 @Order注解,實(shí)現(xiàn)Ordered接口等等。像面對(duì)disconf這種第三方框架類(lèi)的bean,最好是使用@DepensOn 來(lái)控制加載順序
總結(jié)bean的加載還有很多其他的細(xì)節(jié),這里就不一一展開(kāi)了。本文主要專(zhuān)注加載順序,順便聊一下初學(xué)如何去看源碼。總結(jié)起來(lái)就是一句話,小目標(biāo),不拓展。
寫(xiě)到最后才發(fā)現(xiàn)上面的問(wèn)題,加載順序并不是主要原因!!(°?°?) 好吧,下次一定搞清楚了再動(dòng)筆,這里也買(mǎi)一個(gè)關(guān)子,感興趣的童鞋可以自己Debugger找一下原因。這里給個(gè)小提示,是跟代理有關(guān)。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/74477.html
摘要:此文已由作者王慎為授權(quán)網(wǎng)易云社區(qū)發(fā)布。歡迎訪問(wèn)網(wǎng)易云社區(qū),了解更多網(wǎng)易技術(shù)產(chǎn)品運(yùn)營(yíng)經(jīng)驗(yàn)。網(wǎng)易云免費(fèi)體驗(yàn)館,成本體驗(yàn)款云產(chǎn)品更多網(wǎng)易技術(shù)產(chǎn)品運(yùn)營(yíng)經(jīng)驗(yàn)分享請(qǐng)點(diǎn)擊。文章來(lái)源網(wǎng)易云社區(qū) 此文已由作者王慎為授權(quán)網(wǎng)易云社區(qū)發(fā)布。 歡迎訪問(wèn)網(wǎng)易云社區(qū),了解更多網(wǎng)易技術(shù)產(chǎn)品運(yùn)營(yíng)經(jīng)驗(yàn)。 disconf-spring-boot-starter使用方法:引入maven依賴(lài): com.netease.hai...
摘要:有了配置文件之后,啟動(dòng)程序,我們首先可以看到日志輸入,由此可以看出程序讀取了的配置。首先,根據(jù)的全局查找功能,直接搜索這些詞出現(xiàn)的位置,進(jìn)行定位,可以找到這個(gè)日志出現(xiàn)于方法之中。由于我們的配置文件在下,所以只要留意當(dāng)為的程序執(zhí)行情況即可。 前言 上文《一文掌握 Spring Boot Profiles》 是對(duì) Spring Boot Profiles 的介紹和使用,因此本文將從源碼角度...
摘要:總體介紹在互聯(lián)網(wǎng)金融行業(yè)一百多億其實(shí)也算不上大平臺(tái),也就是二級(jí)陣營(yíng)吧,其實(shí)每次的架構(gòu)升級(jí)都是隨著業(yè)務(wù)重大推進(jìn)而伴隨的,在前一代系統(tǒng)架構(gòu)上遇到的問(wèn)題,業(yè)務(wù)開(kāi)發(fā)過(guò)程中積累一些優(yōu)秀的開(kāi)發(fā)案例,在下一代系統(tǒng)開(kāi)發(fā)中就會(huì)大力推進(jìn)架構(gòu)升級(jí)。 回想起從公司成立敲出的第一行代碼算起到現(xiàn)在也快三年了,平臺(tái)的技術(shù)架構(gòu),技術(shù)體系也算是經(jīng)歷了四次比較重大的升級(jí)轉(zhuǎn)化(目前第四代架構(gòu)體系正在進(jìn)行中),臨近年底也想抽...
摘要:總體介紹在互聯(lián)網(wǎng)金融行業(yè)一百多億其實(shí)也算不上大平臺(tái),也就是二級(jí)陣營(yíng)吧,其實(shí)每次的架構(gòu)升級(jí)都是隨著業(yè)務(wù)重大推進(jìn)而伴隨的,在前一代系統(tǒng)架構(gòu)上遇到的問(wèn)題,業(yè)務(wù)開(kāi)發(fā)過(guò)程中積累一些優(yōu)秀的開(kāi)發(fā)案例,在下一代系統(tǒng)開(kāi)發(fā)中就會(huì)大力推進(jìn)架構(gòu)升級(jí)。 回想起從公司成立敲出的第一行代碼算起到現(xiàn)在也快三年了,平臺(tái)的技術(shù)架構(gòu),技術(shù)體系也算是經(jīng)歷了四次比較重大的升級(jí)轉(zhuǎn)化(目前第四代架構(gòu)體系正在進(jìn)行中),臨近年底也想抽...
摘要:除了,還有十余種,有的是特定操作,比如轉(zhuǎn)儲(chǔ)內(nèi)存日志有的是信息展示,比如顯示應(yīng)用健康狀態(tài)。 showImg(http://ww1.sinaimg.cn/large/006tNc79gy1g5qb2coyfoj30u00k0tan.jpg); 前言 隨著線上應(yīng)用逐步采用 SpringBoot 構(gòu)建,SpringBoot應(yīng)用實(shí)例越來(lái)多,當(dāng)線上某個(gè)應(yīng)用需要升級(jí)部署時(shí),常常簡(jiǎn)單粗暴地使用 kil...
閱讀 2820·2023-04-25 15:01
閱讀 3080·2021-11-23 10:07
閱讀 3367·2021-10-12 10:12
閱讀 3458·2021-08-30 09:45
閱讀 2196·2021-08-20 09:36
閱讀 3587·2019-08-30 12:59
閱讀 2436·2019-08-26 13:52
閱讀 934·2019-08-26 13:24