一個人的價值體現在能夠幫助多少人。比如自己編碼好,價值能得到很好的體現。如果你做出來的東西能夠幫助別人開發,大大減少開發的時間,那麼就功德無量。
前言
Spring Boot
是Spring家族具有劃時代意義的一款產品,它發展自Spring Framwork
卻又高於它,這種高於它主要表現在其最重要的三大特性,而其中最最最爲重要的便是Spring Boot的自動配置(AutoConfiguration
)。與其說是自動,倒不如說是智能,該框架看起來好像“更聰明”了。
但任何決定都是一把雙刃劍,Spring Boot
的出現解決了程序員,讓開發效率可以指數級提升。然後它帶來的問題也是因爲它的太智能,使得倘若出了問題就會讓衆多程序員兩眼一抹黑,無從下手。這不,本文將要討論的是Spring Boot
提供給我們的幾個註解:@AutoConfigureBefore、@AutoConfigureAfter、@AutoConfigureOrder
標註在了配置文件上,爲何不生效的問題。
提示:Spring Boot的自動配置是通過@EnableAutoConfiguration這個註解驅動的,默認是開啓狀態。但你也可以通過
spring.boot.enableautoconfiguration = false
來關閉它,回退到Spring Framwork時代。
正文
我們已然知道,在傳統的Spring Framwork
裏,一個@Configuration
標註的類就代表一個配置類,當存在多個@Configuration
時,他們的執行順序是由使用者手動指定的:構造ApplicationContext
傳入的順序就是執行順序。那麼到了Spring Boot下,這一切都變成自動的了,怎麼辦?如何控制配置的先後順序呢?還好,Spring Boot給我們提供了@AutoConfigureBefore、@AutoConfigureAfter、@AutoConfigureOrder
這三個註解旨在幫我們解決這種需求。
說明:這三個註解是Spring Boot提供的而非Spring Framwork。其中前兩個是1.0.0就有了,
@AutoConfigureOrder
屬於1.3.0版本新增的註解,表示絕對順序(數字越小,優先級越高)。另外,這幾個註解並不互斥,可以同時標註在同一個@Configuration
自動配置類上
配置類爲何需要順序?
我們知道Spring對Bean的初始化其實是無序的,並不能通過@Order
註解指定順序值來排序。而對於配置類來說,很多場景下對配置類的執行順序是必須的,因爲前後的Bean(定義)之前存在依賴關係,有且僅當某個前面的加載完成(並不代表初始化完成)後,後面的判斷纔會有意義,所以需要通過這三大註解來輔助實現這種需求。
值得注意的是:Spring對Bean定義信息的收集是按照@Configuration配置文件的順序來的,並且之前是隔離的。也就是說先收集完配置1裏面所有的@Bean定義信息,纔會繼續收集配置2的。使用static提高優先級,有且僅在配置文件內部提高,出了配置文件後無效。
Spring Boot內置的配置順序case舉例
此處便以大家最爲熟悉的WebMvc的自動配置場景爲例:
@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 { ... }
WebMvcAutoConfiguration
執行初始化的條件是:DispatcherServletAutoConfiguration、TaskExecutionAutoConfiguration、ValidationAutoConfiguration
這三個哥們都已經完成初始化DispatcherServletAutoConfiguration
執行初始化的條件是:ServletWebServerFactoryAutoConfiguration
已經完成初始化ServletWebServerFactoryAutoConfiguration
執行初始化的條件是:@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
最高優先級,也就是說它無其它依賴,希望自己是最先被初始化的- 當碰到多個配置都是最高優先級的時候,且互相之前沒有關係的話,順序也是不定的。但若互相之間存在依賴關係(如本利的
DispatcherServletAutoConfiguration
和ServletWebServerFactoryAutoConfiguration
),那就按照相對順序走
- 當碰到多個配置都是最高優先級的時候,且互相之前沒有關係的話,順序也是不定的。但若互相之間存在依賴關係(如本利的
在WebMvcAutoConfiguration
配置完成後,在它之後其實還有很多配置會嘗試執行,例如:
@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 { ... }
這些都很容易理解:模版引擎必須在webMVC全部配置好後纔開始配置。
使用誤區(重點)
說實話,針對這三個註解,實在有太多人把它進行誤用了,想用但是用了卻又不生效,於是就開始“罵娘”動作,這也是我書寫本文的最大動力所在:糾正你的錯誤使用,告訴你正確姿勢。
錯誤使用示例
我見到的大多數小夥伴都這麼來使用它:我這裏使用“僞代碼”進行模擬
@Configuration
public class B_ParentConfig {
B_ParentConfig() {
System.out.println("配置類ParentConfig構造器被執行...");
}
}
@Configuration
public class A_SonConfig {
A_SonConfig() {
System.out.println("配置類SonConfig構造器被執行...");
}
}
@Configuration
public class C_DemoConfig {
public C_DemoConfig(){
System.out.println("我是被自動掃描的配置,初始化啦....");
}
}
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args).close();
}
}
啓動Spring Boot容器,控制檯打印:
配置類SonConfig構造器被執行...
配置類ParentConfig構造器被執行...
我是被自動掃描的配置,初始化啦....
這個時候你就想:讓ParentConfig優先於SonConfig先執行(畢竟先有父母纔有兒子嘛)。因此就想當然的這麼幹:
@AutoConfigureBefore(A_SonConfig.class)
@Configuration
public class B_ParentConfig {
B_ParentConfig() {
System.out.println("配置類ParentConfig構造器被執行...");
}
}
通過@AutoConfigureBefore
控制,表示在A_SonConfig
之前執行此配置。語義上貌似沒有任何問題,再次啓動SB容器:
配置類SonConfig構造器被執行...
配置類ParentConfig構造器被執行...
我是被自動掃描的配置,初始化啦....
what a fuck,順序怎麼還沒有變呢?代碼不會騙人,@AutoConfigureBefore
的語義也沒有問題,而是你使用的姿勢不對,下面給你正確的使用姿勢。
正確使用姿勢
針對這種case要想達到預期效果,你需要如下兩步:
- 把
A_SonConfig
和B_ParentConfig
挪動到Application掃描不到的包內,切記:一定必須是掃描不到的包 - 當前工程內增加配置
META-INF/spring.factories
,內容爲:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.fsx.autoconfig.A_SonConfig,com.fsx.autoconfig.B_ParentConfig
再次啓動Spring Boot容器,打印輸出:
我是被自動掃描的配置,初始化啦....
配置類ParentConfig構造器被執行...
配置類SonConfig構造器被執行...
完美。Parent終於在Son之前完成了初始化,也就是說我們的@AutoConfigureBefore
註解生效了。當然針對此使用姿勢並不是完全沒有“副作用”的,有如下細節需要引起注意:
- 若你不用
@AutoConfigureBefore
這個註解,想依賴於EnableAutoConfiguration
配置的來控制先後順序,答案是不可以的,控制不了 - 還有個現象細節:我每次都故意輸出了
我是被自動掃描的配置,初始化啦....
這句話,然而可以發現被掃描進去(錯誤示例)時實例化是在它前面,而通過spring.factories
方式進去是在它的後面- 從這個現象是可以衍生出結論的:
Spring Boot
的自動配置均是通過spring.factories
來指定的,它的優先級是最低的(執行時機是最晚的)。掃描進來的一般都是你自己自定義的配置類,所以優先級是最高的:肯定在自動配置之前執行- 這也告訴我們:若你要指定掃描的包名,請千萬不要掃描到形如
org.springframework
這種包名,否則“天下大亂”(當然嘍:Spring Boot也有一個類抓們處理防止你配置錯了,具體參見ComponentScanPackageCheck
默認實現)
- 這也告訴我們:若你要指定掃描的包名,請千萬不要掃描到形如
- 從這個現象是可以衍生出結論的:
- 另外再提示一個小細節:請儘量不要讓配置類既被掃描到了,又在
spring.factories
配置了,否則後者會覆蓋前者,容易造成出錯的
所以對於@AutoConfigureBefore
等三個註解的正確使用姿勢是:請使用在你的自動配置裏(一般是你自定義starter時使用),而不是使用在你業務工程中的@Configuration
裏,毫無效果。
@AutoConfigureBefore三大註解解析時機淺析
爲了更好的輔助理解,加強記憶,本文將這三大註解解析時機簡要的說一下,知道了它被解析的時機,自然就很好解釋你心底的那個爲什麼:爲什麼我註解寫在這裏無效呢?
這三個註解的解析都是交給AutoConfigurationSorter
來排序、處理的,類似於AnnotationAwareOrderComparator
去解析排序@Order
註解。
class AutoConfigurationSorter {
// 唯一給外部調用的方法:返回排序好的Names,因此返回的是個List嘛(ArrayList)
List<String> getInPriorityOrder(Collection<String> classNames) {
...
// 先按照自然順序排一波
Collections.sort(orderedClassNames);
// 在按照@AutoConfigureBefore這三個註解排一波
orderedClassNames = sortByAnnotation(classes, orderedClassNames);
return orderedClassNames;
}
...
}
這個排序的“解析”過程還是蠻複雜的,本文點到爲止,觀其大意即可。此排序器被連個地方使用到:
AutoConfigurationImportSelector
:Spring自動配置處理器,用於加載所有的自動配置類。它實現了DeferredImportSelector
接口:這也順便解釋了爲何自動配置是最後執行的原因~AutoConfigurations
:表示自動配置@Configuration類。
綜上解析時機,你可以簡單粗暴的記住結論:@AutoConfigureBefore、@AutoConfigureAfter、@AutoConfigureOrder
這三個註解只能作用於自動配置類,而不能是自定義的@Configuration配置類。
總結
關於Spring Boot自動配置順序相關的三大註解@AutoConfigureBefore、@AutoConfigureAfter、@AutoConfigureOrder
就先介紹到這了,本文用意主要是爲了幫助大家規範此些“常用註解”的使用,規避一些誤區,端正使用姿勢,避免犯錯又丈二和尚。
我看到不少文章、生產代碼都使用錯了(估計有沒有效果自己的都不知道,又或者剛好確實是在xxx後面執行而以爲生效了),希望本文能幫助到你。
聲明
原創不易,碼字不易,多謝你的點贊、收藏、關注。把本文分享到你的朋友圈是被允許的,但拒絕抄襲
。你也可【左邊掃碼/或加wx:fsx641385712】邀請你加入我的 Java高工、架構師 系列羣大家庭學習和交流。
- [享學Feign] 一、原生Feign初體驗,Netflix Feign還是Open Feign?
- [享學Feign] 二、原生Feign的註解介紹及使用示例
- [享學Feign] 三、原生Feign的核心API詳解(一):UriTemplate、HardCodedTarget…
- [享學Feign] 四、原生Feign的核心API詳解(二):Contract、SynchronousMethodHandler…
- [享學Feign] 五、原生Feign的編碼器Encoder、QueryMapEncoder
- [享學Feign] 六、原生Feign的解碼器Decoder、ErrorDecoder
- [享學Feign] 七、請求模版對象RequestTemplate和標準請求對象feign.Request
- [享學Feign] 八、Feign是如何生成接口代理對象的?Feign實例的構建器Feign.Builder詳解
- [享學Feign] 九、Feign + OkHttp和Feign + Apache HttpClient哪個更香?
- [享學Feign] 十、Feign通過feign-jackson模塊天然支持POJO的編碼和解碼
- [享學Feign] 十一、Feign通過feign-slf4j模塊整合logback記錄日誌
- [享學Feign] 十二、Feign通過feign-hystrix模塊使其擁有熔斷、降級能力