今天小夥伴跑過來講,搭建框架的時候出現disconf配置好的信息不可以及時注入到實體類中的狀況。他經過實踐發現,spring 加載Configuration 的時候,經過@Autowired注入的RedisProperties 實體類裏面沒有值。等到容器加載完成後,在Controller 層注入的RedisProperties是有數據的,搞了接近一天。我在他控制檯看到了以下信息(簡化):html
**** DISCONF START FIRST SCAN **** //此處省略 **** DISCONF END FIRST SCAN **** //@configuration 註冊bean的信息(能夠本身添加日誌) **** DISCONF START SECOND SCAN **** //此處省略 **** DISCONF END SECOND SCAN ****java
經過信息能夠看出,關鍵問題出如今了第二次掃描在Bean註冊以後。第二次掃描負責將配置注入實體類中,詳細能夠參考disconf-client設計spring
那麼第二次掃描在何時進行的呢,打開DisconfMgrBeanSecond 類app
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)ide
不少人不太會調試源碼,一上手就從入口函數開始,點幾下就本身犯暈了。還有些人習慣看類圖,從全局去看,也會很累。這裏不是說類圖方式很差,而是分狀況而定。好比你讀 Java 集合框架,類圖就是一個不錯的選擇,一來集合類功能相對獨立,二來集合自己很符合面向對象的思想。面對spring這種名字很類似,代碼龐大的大型框架時,建議仍是以點入面,有目的的去看。這裏介紹一下我本身使用的方法:函數
以下圖源碼分析
Debugger 菜單欄中咱們很容易找到調用棧的信息,觀察這些方法,咱們能夠看到這三個方法的方法名很像咱們想知道的加載過程post
在仔細點開源碼會發現 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 Set<BeanDefinitionHolder> doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
for (String basePackage : basePackages) {
//掃描package,尋找候選組件
Set<BeanDefinition> 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的註解。
上面只是說明白了類文件的註冊順序,他是經過掃描包名,類名這樣排下來的,只是一個初步順序。
先來看一下以前調試的初步順序 testConfig-->helloController-->testController-->helloServiceImpl-->testServiceImpl-->test
總體看下來,他是按照包名和類型排序的,只不過有一點須要注意 test 所在的包其實是在Impl 前面的,且Test類上沒有任何註解,這代表他們的註冊順序實際上是:先掃描Component,在掃描@Bean註解。
當bean真正加載的時候是這樣加載的,每加載一個類,看他有沒有依賴,有的話同時加載依賴bean。這也就解釋了爲何testController爲何跳過impl 直接加載test。
其實有不少方法控制順序,依賴注入提早,@DepensOn 和 @Order註解,實現Ordered接口等等。像面對disconf這種第三方框架類的bean,最好是使用@DepensOn 來控制加載順序
bean的加載還有不少其餘的細節,這裏就不一一展開了。本文主要專一加載順序,順便聊一下初學如何去看源碼。總結起來就是一句話,小目標,不拓展。
寫到最後才發現上面的問題,加載順序並非主要緣由!!(°ロ°٥) 好吧,下次必定搞清楚了再動筆,這裏也買一個關子,感興趣的童鞋能夠本身Debugger找一下緣由。這裏給個小提示,是跟代理有關。