你瞭解Spring Boot的自動配置嗎?爲何我的@AutoConfigureBefore註解不生效?

一個人的價值體現在能夠幫助多少人。比如自己編碼好,價值能得到很好的體現。如果你做出來的東西能夠幫助別人開發,大大減少開發的時間,那麼就功德無量。

前言

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)最高優先級,也就是說它無其它依賴,希望自己是最先被初始化的
    • 當碰到多個配置都是最高優先級的時候,且互相之前沒有關係的話,順序也是不定的。但若互相之間存在依賴關係(如本利的DispatcherServletAutoConfigurationServletWebServerFactoryAutoConfiguration),那就按照相對順序走
      在這裏插入圖片描述

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要想達到預期效果,你需要如下兩步:

  1. A_SonConfigB_ParentConfig挪動到Application掃描不到的包內,切記:一定必須是掃描不到的包
  2. 當前工程內增加配置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高工、架構師 系列羣大家庭學習和交流。
往期精選

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章