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