
前言
Spring Boot是Spring家族具有劃時(shí)代意義的一款產(chǎn)品,它發(fā)展自Spring Framework卻又高于它,這種高于主要表現(xiàn)在其最重要的三大特性,而相較于這三大特性中更為重要的便是Spring Boot的自動(dòng)配置(AutoConfiguration)。與其說(shuō)是自動(dòng),倒不如說(shuō)是“智慧”,該框架看起來(lái)好像“更聰明”了。因此它也順理成章的成為了構(gòu)建微服務(wù)的基礎(chǔ)設(shè)施,穩(wěn)坐第一寶座。
生活之道,在于取舍。程序設(shè)計(jì)何嘗不是,任何決定都會(huì)是一把雙刃劍,Spring Boot的自動(dòng)配置解決了Spring Framework使用起來(lái)的眾多痛點(diǎn),讓開(kāi)發(fā)效率可以得到指數(shù)級(jí)提升(想一想,這不就是功德無(wú)量嗎?) 。成也蕭何敗也蕭何,也正是因?yàn)樗奶腔?,倘若出了?wèn)題就會(huì)讓程序設(shè)計(jì)師兩眼一抹黑,無(wú)從下手。
瑕不掩瑜,Spring Boot前進(jìn)的步伐浩浩蕩蕩,學(xué)就完了
這不,我就在前幾天收到一個(gè)“求助”,希望使用@AutoConfigureBefore來(lái)控制配置的順序,但并未能如愿。本文就針對(duì)這個(gè)場(chǎng)景case稍作展開(kāi),討論下使用@AutoConfigureBefore、@AutoConfigureAfter、@AutoConfigureOrder三大注解控制自動(dòng)配置執(zhí)行順序的正確姿勢(shì)。
提示:Spring Boot的自動(dòng)配置是通過(guò)@EnableAutoConfiguration注解驅(qū)動(dòng)的,預(yù)設(shè)是開(kāi)啟狀態(tài)。你也可以通過(guò)
spring.boot.enableautoconfiguration = false來(lái)關(guān)閉它,回退到Spring Framework時(shí)代。顯然這不是本文需要討論的內(nèi)容~

正文
本文將要聊的重點(diǎn)是Spring Boot自動(dòng)配置+ 順序控制,自動(dòng)配置大家都耳熟能詳,那么“首當(dāng)其沖”就是知曉這個(gè)問(wèn)題:配置類(lèi)的執(zhí)行為何需要控制順序?
配置類(lèi)為何需要順序?
我們已經(jīng)知道Spring容器它對(duì)Bean的初始化是無(wú)序的,我們并不能想當(dāng)然的通過(guò)@Order注解來(lái)控制其執(zhí)行順序。一般來(lái)說(shuō),對(duì)于容器內(nèi)普通的Bean我們只需要關(guān)注依賴(lài)關(guān)系即可,而并不需要關(guān)心其絕對(duì)的順序,而依賴(lài)關(guān)系的管理Spring的是做得很好的,這不連回圈依賴(lài)它都可以搞定么。
@Configuration配置類(lèi)它也是一個(gè)Bean,但對(duì)于配置類(lèi)來(lái)說(shuō),某些場(chǎng)景下的執(zhí)行順序是必須的,是需要得到保證的。比如很典型的一個(gè)非A即B的case:若容器內(nèi)已經(jīng)存在A(yíng)了,就不要再把B放進(jìn)來(lái)。這種case即使用中文理解,就能知道對(duì)A的“判斷”必須要放在B的前面,否則可能導(dǎo)致程序出問(wèn)題。
那么針對(duì)于配置的執(zhí)行順序,傳統(tǒng)Spring和Spring Boot下各自是如何處理的,表現(xiàn)如何呢?
Spring下控制配置執(zhí)行順序
在傳統(tǒng)的Spring Framework里,一個(gè)@Configuration注解標(biāo)注的類(lèi)就代表一個(gè)配置類(lèi),當(dāng)存在多個(gè)@Configuration時(shí),他們的執(zhí)行順序是由使用者靠手動(dòng)指定的,就像這樣:
// 手動(dòng)控制Config1 Config2的順序
ApplicationContext context = new AnnotationConfigApplicationContext(Config1.class, Config2.class);
當(dāng)然,你可能就疑問(wèn)了說(shuō):即使在傳統(tǒng)Spirng里,我也從沒(méi)有自己使用過(guò)AnnotationConfigApplicationContext來(lái)顯示載入配置啊,都是使用@Configuration定義好配置類(lèi)后,點(diǎn)選Run一把唆的。沒(méi)錯(cuò),那是因?yàn)槟闶窃趙eb環(huán)境下使用Spring,IoC容器是借助web容器(如Tomcat等)來(lái)驅(qū)動(dòng)的,Spring對(duì)此部分封裝得非常好,所以做到了對(duì)使用者幾乎無(wú)感知。
關(guān)于這部分的內(nèi)容,此處就不深究了,畢竟本文重點(diǎn)不在這嘛。但可以給出給小結(jié)論:@Configuration配置被載入進(jìn)容器的方式大體上可分為兩種:
- 手動(dòng)。構(gòu)建
ApplicationContext時(shí)由構(gòu)建者手動(dòng)傳入,可手動(dòng)控制順序 - 自動(dòng)。被
@ComponentScan自動(dòng)掃描進(jìn)去,無(wú)法控制順序
絕大多數(shù)情況下我們都是使用自動(dòng)的方式,所以在Spring下對(duì)配置的順序并無(wú)感知。其實(shí)這也是需求驅(qū)使,因?yàn)樵趥鹘y(tǒng)Spring下我們并無(wú)此需求,所以對(duì)它無(wú)感是合乎邏輯的。另說(shuō)一句,雖然我們并不能控制Bean的順序,但是我們是可以干涉它的,比如:控制依賴(lài)關(guān)系、提升優(yōu)先順序、“間接”控制執(zhí)行順序...當(dāng)然嘍這是后面文章的內(nèi)容,敬請(qǐng)關(guān)注。
Spring Boot下控制配置執(zhí)行順序
Spring Boot下對(duì)自動(dòng)配置的管理對(duì)比于Spring它就是黑盒,它會(huì)根據(jù)當(dāng)前容器內(nèi)的情況來(lái)動(dòng)態(tài)的判斷自動(dòng)配置類(lèi)的載入與否、以及載入的順序,所以可以說(shuō):Spring Boot的自動(dòng)配置它對(duì)順序是有強(qiáng)要求的。需求驅(qū)使,Spring Boot給我們提供了@AutoConfigureBefore、@AutoConfigureAfter、@AutoConfigureOrder(下面統(tǒng)稱(chēng)這三個(gè)注解為“三大注解”)這三個(gè)注解來(lái)幫我們解決這種訴求。
需要注意的是:三大注解是Spring Boot提供的而非Spring Framework。其中前兩個(gè)是1.0.0就有了,@AutoConfigureOrder屬于1.3.0版本新增,表示絕對(duì)順序(數(shù)字越小,優(yōu)先順序越高)。另外,這幾個(gè)注解并不互斥,可以同時(shí)標(biāo)注在同一個(gè)@Configuration自動(dòng)配置類(lèi)上。
Spring Boot內(nèi)建的控制配置順序舉例
為方便大家理解,我列出一個(gè)Spring Boot它自己的使用作為示例學(xué)一學(xué)。以大家最為熟悉的WebMvc的自動(dòng)配置場(chǎng)景為例:
@Configuration(proxyBeanMethods = false)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration { ... }
@Configuration(proxyBeanMethods = false)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)
public class DispatcherServletAutoConfiguration { ... }
@Configuration(proxyBeanMethods = false)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
public class ServletWebServerFactoryAutoConfiguration { ... }
這幾個(gè)配置是WebMVC的核心配置,他們之間是有順序關(guān)系的:
-
WebMvcAutoConfiguration被載入的前提是:DispatcherServletAutoConfiguration、TaskExecutionAutoConfiguration、ValidationAutoConfiguration這三個(gè)哥們都已經(jīng)完成初始化 -
DispatcherServletAutoConfiguration被載入的前提是:ServletWebServerFactoryAutoConfiguration已經(jīng)完成初始化 -
ServletWebServerFactoryAutoConfiguration被載入的前提是:@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)最高優(yōu)先順序,也就是說(shuō)它無(wú)其它依賴(lài),希望自己是最先被初始化的- 當(dāng)碰到多個(gè)配置都是最高優(yōu)先順序的時(shí)候,且互相之前沒(méi)有關(guān)系的話(huà),順序也是不定的。但若互相之間存在依賴(lài)關(guān)系(如本利的
DispatcherServletAutoConfiguration和ServletWebServerFactoryAutoConfiguration),那就按照相對(duì)順序走
- 當(dāng)碰到多個(gè)配置都是最高優(yōu)先順序的時(shí)候,且互相之前沒(méi)有關(guān)系的話(huà),順序也是不定的。但若互相之間存在依賴(lài)關(guān)系(如本利的

在WebMvcAutoConfiguration載入后,在它之后其實(shí)還有很多配置會(huì)嘗試執(zhí)行,例如:
@AutoConfigureAfter(WebMvcAutoConfiguration.class)
class FreeMarkerServletWebConfiguration extends AbstractFreeMarkerConfiguration { ... }
@AutoConfigureAfter(WebMvcAutoConfiguration.class)
public class GroovyTemplateAutoConfiguration { ... }
@AutoConfigureAfter({ WebMvcAutoConfiguration.class, WebFluxAutoConfiguration.class })
public class ThymeleafAutoConfiguration { ... }
@AutoConfigureAfter(WebMvcAutoConfiguration.class)
public class LifecycleMvcEndpointAutoConfiguration { ... }
這些都很容易理解:如果都不是Web環(huán)境,載入一些模版引擎的并無(wú)必要嘛。
三大注解使用的誤區(qū)(重要)
根據(jù)我的切身體會(huì),針對(duì)這三大注解,實(shí)在有太多人把它誤用了,想用但是用了卻又不生效,于是就容易觸發(fā)一波“罵街”操作,其實(shí)這也是我書(shū)寫(xiě)本文的最大動(dòng)力所在:糾正你的錯(cuò)誤使用,告訴你正確姿勢(shì)。
錯(cuò)誤使用示例
我見(jiàn)到的非常多的小伙伴這么來(lái)使用三大注解:我這里使用“虛擬碼”進(jìn)行模擬
@Configuration
public class B_ParentConfig {
B_ParentConfig() {
System.out.println("配置類(lèi)ParentConfig構(gòu)造器被執(zhí)行...");
}
}
@Configuration
public class A_SonConfig {
A_SonConfig() {
System.out.println("配置類(lèi)SonConfig構(gòu)造器被執(zhí)行...");
}
}
@Configuration
public class C_DemoConfig {
public C_DemoConfig(){
System.out.println("我是被自動(dòng)掃描的配置,初始化啦....");
}
}
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args).close();
}
}
通過(guò)名稱(chēng)能知道我想要的達(dá)到的效果是:ParentConfig先載入,SonConfig后載入。(DemoConfig作為一個(gè)參考配置,作為日志參考使用即可)
啟動(dòng)應(yīng)用,控制臺(tái)列?。?/p>
配置類(lèi)SonConfig構(gòu)造器被執(zhí)行...
配置類(lèi)ParentConfig構(gòu)造器被執(zhí)行...
我是被自動(dòng)掃描的配置,初始化啦....
Son優(yōu)先于Parent被載入了,這明顯不符合要求。因此,我看到很多小伙伴就這么干:
@AutoConfigureBefore(A_SonConfig.class)
@Configuration
public class B_ParentConfig {
B_ParentConfig() {
System.out.println("配置類(lèi)ParentConfig構(gòu)造器被執(zhí)行...");
}
}
通過(guò)@AutoConfigureBefore控制,表示在A_SonConfig之前執(zhí)行此配置。語(yǔ)義層面上看,貌似沒(méi)有任何問(wèn)題,再次啟動(dòng)應(yīng)用:
配置類(lèi)SonConfig構(gòu)造器被執(zhí)行...
配置類(lèi)ParentConfig構(gòu)造器被執(zhí)行...
我是被自動(dòng)掃描的配置,初始化啦....
what a fuck。看到?jīng)],我沒(méi)騙你吧,罵街了罵街了

竟然沒(méi)生效?程序不會(huì)騙人,
@AutoConfigureBefore的語(yǔ)義也沒(méi)有問(wèn)題,而是你使用的姿勢(shì)不對(duì),下面我會(huì)給你正確姿勢(shì)。
三大注解使用的正確姿勢(shì)
針對(duì)以上case,要想達(dá)到預(yù)期效果,正確姿勢(shì)只需要下面兩步:
- 把
A_SonConfig和B_ParentConfig挪動(dòng)到Application掃描不到的包內(nèi),切記:一定且必須是掃描不到的包內(nèi) - 當(dāng)前工程里增加配置
META-INF/spring.factories,內(nèi)容為(配置里Son和Parent前后順序?qū)Y(jié)果無(wú)影響):
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.fsx.autoconfig.A_SonConfig,com.fsx.autoconfig.B_ParentConfig
再次啟動(dòng)應(yīng)用看看,列印輸出:
我是被自動(dòng)掃描的配置,初始化啦....
配置類(lèi)ParentConfig構(gòu)造器被執(zhí)行...
配置類(lèi)SonConfig構(gòu)造器被執(zhí)行...
完美。符合預(yù)期,Parent終于在Son之前完成了初始化,也就是說(shuō)我們的@AutoConfigureBefore注解生效了。
使用細(xì)節(jié)注意事項(xiàng)
針對(duì)此使用姿勢(shì),雖然很正確,并不是完全沒(méi)有“副作用”的,有如下細(xì)節(jié)平時(shí)也需要引起注意:
- 若你不用
@AutoConfigureBefore這個(gè)注解,單單就想依賴(lài)于spring.factories里的先后順序的來(lái)控制實(shí)際的載入順序,答案是不可以,控制不了 - 例子中有個(gè)小細(xì)節(jié):我每次都故意輸出了
我是被自動(dòng)掃描的配置,初始化啦....這句話(huà),可以發(fā)現(xiàn)被掃描進(jìn)去配置例項(xiàng)化是在它前面(見(jiàn)錯(cuò)誤示例),而通過(guò)spring.factories方式進(jìn)去是在它的后面(見(jiàn)正確姿勢(shì)) - 從這個(gè)小細(xì)節(jié)可以衍生得到結(jié)論:
Spring Boot的自動(dòng)配置均是通過(guò)spring.factories來(lái)指定的,它的優(yōu)先順序最低(執(zhí)行時(shí)機(jī)是最晚的);通過(guò)掃描進(jìn)來(lái)的一般都是你自己自定義的配置類(lèi),所以?xún)?yōu)先順序是最高的,肯定在自動(dòng)配置之前載入- 從這你應(yīng)該學(xué)到:若你要指定掃描的包名,請(qǐng)千萬(wàn)不要掃描到形如
org.springframework這種包名,否則“天下大亂”(當(dāng)然嘍為了防止這種情況出現(xiàn),Spring Boot做了容錯(cuò)的。它有一個(gè)類(lèi)專(zhuān)門(mén)檢測(cè)這個(gè)case防止你配置錯(cuò)了,具體參見(jiàn)ComponentScanPackageCheck預(yù)設(shè)實(shí)現(xiàn))
- 從這你應(yīng)該學(xué)到:若你要指定掃描的包名,請(qǐng)千萬(wàn)不要掃描到形如
- 請(qǐng)盡量不要讓自動(dòng)配置類(lèi)既被掃描到了,又放在
spring.factories配置了,否則后者會(huì)覆蓋前者,很容易造成莫名其妙的錯(cuò)誤
小總結(jié),對(duì)于三大注解的正確使用姿勢(shì)是應(yīng)該是:請(qǐng)使用在你的自動(dòng)配置里(一般是你自定義starter時(shí)使用),而不是使用在你業(yè)務(wù)工程中的@Configuration里,因?yàn)槟菚?huì)毫無(wú)效果。

三大注解解析時(shí)機(jī)淺析
為了更好的輔助理解,加強(qiáng)記憶,本文將這三大注解解析時(shí)機(jī)簡(jiǎn)要的絮叨一下,知道了它被解析的時(shí)機(jī),自然就很好解釋為何你那么寫(xiě)是無(wú)效的嘍。
這三個(gè)注解的解析都是交給AutoConfigurationSorter來(lái)排序、處理的,做法類(lèi)似于AnnotationAwareOrderComparator去解析排序@Order注解。核心代碼如下:
class AutoConfigurationSorter {
// 唯一給外部呼叫的方法:返回排序好的Names,因此返回的是個(gè)List嘛(ArrayList)
List<String> getInPriorityOrder(Collection<String> classNames) {
...
// 先按照自然順序排一波
Collections.sort(orderedClassNames);
// 在按照@AutoConfigureBefore這三個(gè)註解排一波
orderedClassNames = sortByAnnotation(classes, orderedClassNames);
return orderedClassNames;
}
...
}
此排序器被兩個(gè)地方使用到:
-
AutoConfigurationImportSelector:Spring自動(dòng)配置處理器,用于載入所有的自動(dòng)配置類(lèi)。它實(shí)現(xiàn)了DeferredImportSelector介面:這也順便解釋了為何自動(dòng)配置是最后執(zhí)行的原因~ -
AutoConfigurations:表示自動(dòng)配置@Configuration類(lèi)。
這個(gè)排序的“解析/排序”過(guò)程還是比較復(fù)雜的,本文點(diǎn)到為止,觀(guān)其大意即可。你可以簡(jiǎn)單粗暴的記住結(jié)論:@AutoConfigureBefore、@AutoConfigureAfter、@AutoConfigureOrder這三個(gè)注解只能作用于自動(dòng)配置類(lèi),而不能是自定義的@Configuration配置類(lèi)。
總結(jié)
關(guān)于Spring Boot自動(dòng)配置順序相關(guān)的三大注解@AutoConfigureBefore、@AutoConfigureAfter、@AutoConfigureOrder就先介紹到這了,本文主要用意是為了幫助大家規(guī)范此些“常用注解”的使用,規(guī)避一些誤區(qū),端正使用姿勢(shì),避免犯錯(cuò)時(shí)又丈二和尚。
我看到不少文章、生產(chǎn)上的程序都使用錯(cuò)了(估計(jì)有沒(méi)有效果自己的都不知道,又或者剛好歪打正著確實(shí)是在xxx后面執(zhí)行而以為生效了),希望本文能幫助到你。本文轉(zhuǎn)載自:https://www.yourbatman.cn/x2y/3aa2234d.html
歡迎關(guān)注我的博客,里面有很多精品合集
- 本文轉(zhuǎn)載注明出處(必須帶連接,不能只轉(zhuǎn)文字):字母哥博客。
覺(jué)得對(duì)您有幫助的話(huà),幫我點(diǎn)贊、分享!您的支持是我不竭的創(chuàng)作動(dòng)力! 。另外,筆者最近一段時(shí)間輸出了如下的精品內(nèi)容,期待您的關(guān)注。