Spring Boot 中的手動 Bean 定義

工程 | Dave Syer | 2019 年 1 月 21 日 | ...

假設您想要使用 Spring Boot,但是您不想要 @EnableAutoConfiguration。您究竟應該怎麼做?在先前的文章中,我展示了 Spring 本質上是快速且輕量級的,但是改善啟動時間的簡短建議之一是考慮手動導入 Spring Boot 自動配置,而不是自動吸納所有配置。這不見得適用於所有應用程式,但它可能會有所幫助,並且了解選項有哪些肯定沒有壞處。在這篇文章中,我們將探討各種手動配置的方法並評估其影響。

完整自動配置:Hello World WebFlux

作為基準,讓我們看看一個具有單一 HTTP 端點的 Spring Boot 應用程式

@SpringBootApplication
@RestController
public class DemoApplication {

  @GetMapping("/")
  public Mono<String> home() {
    return Mono.just("Hello World");
  }

  public void main(String[] args) {
    SpringApplication.run(DemoApplication.class, args);
  }

}

如果您使用先前文章中建議的所有調整來執行此應用程式,它應該在一秒左右啟動,或者稍微長一點,具體取決於您的硬體。它在那段時間內做了很多事情 - 設定日誌系統、讀取並綁定到組態檔、啟動 Netty 並監聽 8080 埠,為應用程式中的 @GetMapping 提供路由,並提供預設錯誤處理。如果 Spring Boot Actuator 在類別路徑上,您還會獲得 /health 和 /info 端點(並且啟動時間會稍長一些,因為這個原因)。

@SpringBootApplication 註解,如果您不知道的話,它使用 @EnableAutoConfiguration 進行元註解,這就是免費提供所有有用功能的原因。這就是 Spring Boot 受歡迎的原因,所以我們不想失去任何功能,但我們可以仔細看看實際發生了什麼,也許可以手動完成一些操作,看看我們是否能學到什麼。

注意

如果您想試用此程式碼,可以從 Spring Initializr 輕鬆取得一個空的 WebFlux 應用程式。只需選取「Reactive Web」核取方塊並下載專案即可。

手動導入自動配置

雖然 @EnableAutoConfiguration 功能使向應用程式添加功能變得容易,但它也奪走了一些對啟用哪些功能的控制權。大多數人很樂意做出這種妥協 - 易用性勝過控制權的喪失。潛在的效能損失 - 應用程式的啟動速度可能會稍慢,因為 Spring Boot 必須做一些工作來尋找所有這些功能並安裝它們。事實上,尋找正確的功能並不需要大量精力:沒有類別路徑掃描,並且在經過仔細最佳化後,條件評估非常快。此應用程式啟動時間的大部分(約 80% 或更多)被 JVM 載入類別所佔用,因此實際上使其啟動更快的唯一方法是要求它少做一些事情,即安裝更少的功能。

始終可以使用 @EnableAutoConfiguration 註解中的 exclude 屬性來停用自動配置。某些個別的自動配置也有自己的布林組態標誌,可以從外部設定,例如,對於 JMX,我們可以使用 spring.jmx.enabled=false(例如,作為系統屬性或在屬性檔中)。我們可以沿著這條路走下去,手動關閉我們不想使用的所有功能,但這有點笨拙,並且無法阻止在類別路徑變更時啟動其他功能。

相反,讓我們看看我們可以使用現有的自動配置類別做些什麼,但只應用我們知道我們想要使用的那些,對應於我們喜歡的功能。我們可以將此稱為「點菜」方法,而不是完整自動配置附帶的「吃到飽」。自動配置類別只是普通的 @Configuration,因此原則上我們可以將它們 @Import 到不使用 @EnableAutoConfiguration 的應用程式中。

警告

在閱讀本文的其餘部分之前,請勿執行此操作。這不是使用 Spring Boot 自動配置的正確方法。它可能會破壞某些東西,但一如既往,您的結果可能會有所不同。

例如,以下是上面的應用程式,具有我們想要的所有功能(不包括 Actuator)

@SpringBootConfiguration
@Import({
    WebFluxAutoConfiguration.class,
    ReactiveWebServerFactoryAutoConfiguration.class,
    ErrorWebFluxAutoConfiguration.class,
    HttpHandlerAutoConfiguration.class,
    ConfigurationPropertiesAutoConfiguration.class,
    PropertyPlaceholderAutoConfiguration.class
})
@RestController
public class DemoApplication {

  @GetMapping("/")
  public Mono<String> home() {
    return Mono.just("Hello World");
  }

  public void main(String[] args) {
    SpringApplication.run(DemoApplication.class, args);
  }

}

此版本的應用程式仍然具有我們上面描述的所有功能,但啟動速度會更快(可能快 30% 左右)。那麼,為了獲得更快的啟動速度,我們放棄了什麼?以下是快速概述

  • Spring Boot 自動配置的完整功能集包括其他在實際應用程式中可能實際需要的東西,而不是特定的微小範例。換句話說,30% 的加速不適用於所有應用程式,您的結果可能會有所不同。

  • 手動配置是脆弱的,並且難以猜測。如果您編寫另一個做稍微不同事情的應用程式,您將需要不同的配置導入。您可以透過將其提取到便利類別或註解中並重複使用來減輕此問題。

  • @Import 的行為與 @EnableAutoConfiguration 在配置類別的排序方面不同。如果某些類別具有依賴於早期類別的條件行為,則順序在 @Import 中很重要。為了減輕此問題,您只需要小心。

  • 在典型的真實世界應用程式中,還有另一個排序問題。為了模擬 @EnableAutoConfiguration 的行為,您需要先處理使用者配置,以便它們可以覆寫 Spring Boot 中的條件配置。如果您使用 @ComponentScan,您無法控制掃描的順序,也無法控制與 @Imports 相比這些類別的處理順序。您可以透過使用不同的註解來減輕此問題(請參閱下文)。

  • Spring Boot 自動配置實際上從未設計成以這種方式使用,這樣做可能會在您的應用程式中引入細微的錯誤。對此唯一的補救措施是徹底測試它是否以您期望的方式工作,並謹慎對待升級。

新增 Actuator

如果 Actuator 在類別路徑上,我們也可以新增它們

@SpringBootConfiguration
@Import({
    WebFluxAutoConfiguration.class,
    ReactiveWebServerFactoryAutoConfiguration.class,
    ErrorWebFluxAutoConfiguration.class,
    HttpHandlerAutoConfiguration.class,
    EndpointAutoConfiguration.class,
    HealthIndicatorAutoConfiguration.class, HealthEndpointAutoConfiguration.class,
    InfoEndpointAutoConfiguration.class, WebEndpointAutoConfiguration.class,
    ReactiveManagementContextAutoConfiguration.class,
    ManagementContextAutoConfiguration.class,
    ConfigurationPropertiesAutoConfiguration.class,
    PropertyPlaceholderAutoConfiguration.class
})
@RestController
public class DemoApplication {

  @GetMapping("/")
  public Mono<String> home() {
    return Mono.just("Hello World");
  }

  public void main(String[] args) {
    SpringApplication.run(DemoApplication.class, args);
  }

}

相對於完整的 @EndpointAutoConfiguration 應用程式,此應用程式啟動速度甚至更快(甚至可能快 50%),因為我們僅包含與兩個預設端點相關的配置。Spring Boot 預設啟動所有端點,但不將它們公開給 HTTP。如果我們只關心 /health 和 /info,那是浪費,但當然它也將許多真正有用的功能放在桌面上。

注意

Spring Boot 未來很可能會做更多事情來停用尚未公開或尚未使用的 Actuator。例如,請參閱關於 延遲 Actuator條件端點 的問題(這已在 Spring Boot 2.1.2 中)。

有什麼區別?

手動配置的應用程式有 51 個 Bean,而完全載入的自動配置應用程式有 107 個 Bean(不包括 Actuator)。因此,它啟動速度更快一點也許並不令人意外。在我們繼續討論實作範例應用程式的不同方法之前,讓我們先看看為了使其啟動速度更快,我們遺漏了什麼。如果您列出兩個應用程式中的 Bean 定義,您會看到所有差異都來自我們遺漏的自動配置,而這些自動配置不會被 Spring Boot 條件性排除。以下是列表(假設您正在使用 spring-boot-start-webflux,且沒有手動排除)

AutoConfigurationPackages
CodecsAutoConfiguration
JacksonAutoConfiguration
JmxAutoConfiguration
ProjectInfoAutoConfiguration
ReactorCoreAutoConfiguration
TaskExecutionAutoConfiguration
TaskSchedulingAutoConfiguration
ValidationAutoConfiguration
HttpMessageConvertersAutoConfiguration
RestTemplateAutoConfiguration
WebClientAutoConfiguration

因此,我們不需要(但無論如何)的 12 個自動配置,導致自動配置應用程式中額外增加了 56 個 Bean。它們都提供有用的功能,因此我們可能有一天會想再次包含它們,但現在讓我們假設我們很樂意在沒有它們正在做的任何事情的情況下生活。

注意

spring-boot-autoconfigure 有 122 個自動配置(spring-boot-actuator-autoconfigure 中還有更多),而上面完全載入的自動配置範例應用程式僅使用了其中的 18 個。計算要使用哪些配置發生得很早,並且大多數配置在任何類別載入之前就被 Spring Boot 丟棄了。它非常快速(幾毫秒)。

Spring Boot 自動配置導入

與使用者配置(必須先應用)和自動配置之間的差異相關聯的排序問題可以透過使用不同的註解來部分解決。Spring Boot 為此提供了一個註解:@ImportAutoConfiguration,它來自 spring-boot-autoconfigure,但在與 Spring Boot Test 一起提供的 測試切片 功能中使用。因此,您可以將上面範例中的 @Import 註解替換為 @ImportAutoConfiguration,其效果是將自動配置的處理延遲到所有使用者配置(例如,透過 @ComponentScan@Import 取得)之後。

如果我們準備將自動配置列表整理到自訂註解中,我們甚至可以更進一步。我們可以編寫一個自訂註解,而不是只是將它們複製到明確的 @ImportAutoConfiguration 中,如下所示

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@ImportAutoConfiguration
public @interface EnableWebFluxAutoConfiguration {
}

此註解的主要功能是它使用 @ImportAutoConfiguration 進行元註解。有了這個,我們就可以將新的註解添加到我們的應用程式中

@SpringBootConfiguration
@EnableWebFluxAutoConfiguration
@RestController
public class DemoApplication {

  @GetMapping("/")
  public Mono<String> home() {
    return Mono.just("Hello World");
  }

  public void main(String[] args) {
    SpringApplication.run(DemoApplication.class, args);
  }

}

並在 /META-INF/spring.factories 中列出實際的配置類別

com.example.config.EnableWebFluxAutoConfiguration=\
org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.error.ErrorWebFluxAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration

這樣做的好處是應用程式程式碼不再需要手動列舉配置,而且排序現在由 Spring Boot 負責處理(屬性檔案條目在使用前已排序)。缺點是它僅適用於需要精確這些功能的應用程式,並且在任何想要做一些不同事情的應用程式中都必須替換或擴充它。不過,它仍然很快 - Spring Boot 為簿記(排序和排序)做了一些額外的工作,但並不多。在合適的硬體上使用合適的 JVM 標誌,它可能仍然在不到 700 毫秒的時間內啟動。

函數式 Bean 定義

在先前的文章中,我提到函數式 Bean 定義將是使用 Spring 啟動應用程式的最有效方法。情況仍然如此,我們可以透過將所有 Spring Boot 自動配置重寫為 ApplicationContextInitializers,從此應用程式中擠出額外的 10% 左右。您可以手動執行此操作,也可以使用一些已為您準備好的初始化器,只要您不介意嘗試一些實驗性功能即可。目前有 2 個專案正在積極探索基於函數式 Bean 定義的新工具和新程式設計模型的想法:Spring FuSpring Init。兩者都至少提供了一組最小的函數式 Bean 定義,以取代或包裝 Spring Boot 自動配置。Spring Fu 基於 API (DSL),並且不使用反射或註解。Spring Init 具有函數式 Bean 定義,並且還具有用於「點菜」配置的基於註解的程式設計模型的原型。兩者都在其他地方進行了更詳細的介紹。

這裡需要注意的重點是函數式 Bean 定義速度更快,但如果這是您主要關心的問題,請記住這只是一個 10% 的效果。一旦您將我們在上面剝離的所有功能放回應用程式中,您就會回到載入所有必要類別,並且大致回到與以前相同的啟動時間。換句話說,執行時的 @Configuration 處理成本並非完全可以忽略不計,但也不是很高(在這些微小應用程式中約為 10%,或約 100 毫秒)。

摘要與未來方向

以下圖表總結了來自不同應用程式 Spring PetClinic 的一些基準測試結果

pubchart?oid=1003506885&format=image

圖 1. Petclinic 啟動時間(秒)

它不是一個「真實」的應用程式,但它比簡單的範例更重,並且在執行時使用了更多的功能(例如,Hibernate),因此它在某種程度上更真實。有兩個版本,「demo」和「actr」,後者只是相同的,但帶有 Actuator。對於這兩個範例,最快的啟動時間是黃點,即函數式 Bean 定義,但僅落後 10%(在此應用程式中約為 200 毫秒)的是「點菜」選項(綠色和紅色)。綠色使用自訂註解,例如上面的 @EnableWebFluxAutoConfiguration。紅色是另一種「點菜」選項,其中可以透過不同的自訂註解將自動配置組一起導入,目前命名為 @SpringInitApplication,並且正在 Spring Init 中進行原型設計。藍色是完全載入的自動配置(開箱即用的 Spring Boot)。

Spring Boot 自動配置非常方便,但可以將其描述為「吃到飽」。目前(截至 2.1.x),它可能提供的功能比某些應用程式使用或需要的功能更多。在「點菜」方法中,您可以將 Spring Boot 用作已準備好和預先測試的配置的便捷集合,並選擇您使用的部分。如果您這樣做,那麼 @ImportAutoConfiguration 是工具包的重要組成部分,但隨著我們進一步研究此主題,您應該如何最好地使用它可能會發生變化。未來版本的 Spring Boot,以及可能像 Spring Fu 或 Spring Init 等其他新專案,將使縮小執行時使用的配置選擇範圍變得更容易,無論是自動地還是透過明確的選擇。歸根結底,執行時的 @Configuration 處理並非免費,但也不是特別昂貴(尤其是在 Spring Boot 2.1.x 中)。您使用的功能越少,載入的類別就越少,這會導致更快的啟動速度。歸根結底,我們不希望 @EnableAutoConfiguration 失去其價值或受歡迎程度,並記住您的結果可能會有所不同:本文中的 PetClinic 和簡單範例並不能指導您對更大、更複雜的應用程式的期望。

取得 Spring 電子報

隨時關注 Spring 電子報

訂閱

搶先一步

VMware 提供培訓和認證,以加速您的進展。

了解更多

取得支援

Tanzu Spring 在一個簡單的訂閱中為 OpenJDK™、Spring 和 Apache Tomcat® 提供支援和二進位檔案。

了解更多

即將到來的活動

查看 Spring 社群中所有即將到來的活動。

查看全部