建立自己的定製的Spring Boot Starter快速指南

banq發表於2019-09-07

通過一個例子來了解如何實現一個Spring Boot Starter。對於我們正在構建的每個Spring Boot應用程式,我們不希望從頭開始實現某些跨領域的問題。相反,我們希望一次性實現這些功能,並根據需要將它們作為元件包含在任何應用程式中。

在Spring Boot中,用於提供此類交叉問題的模組的術語是“starter”。Spring Boot starter的一些示例用例是:

  • 提供可配置和/或預設的日誌記錄配置,或使其易於登入到中央日誌伺服器
  • 提供可配置和/或預設的安全配置
  • 提供可配置和/或預設的錯誤處理策略
  • 為中央訊息傳遞基礎設施提供介面卡
  • 整合第三方庫並使其可配置為與Spring Boot一起使用
  • ...

在本文中,我們將構建一個Spring Boot啟動器,它允許Spring Boot應用程式通過虛構的中央訊息傳遞基礎架構輕鬆傳送和接收事件。

在我們深入瞭解建立Spring Boot Starter的細節之前,讓我們討論一些有助於理解Starter工作的關鍵字。

什麼是應用程式上下文?

在Spring應用程式中,應用程式上下文是組成應用程式的物件(或“bean”)的網路。它包含我們的Web控制器,服務,儲存庫以及我們的應用程式可能需要的任何(通常是無狀態的)物件。

什麼是配置?

使用註釋@Configuration標註的類,扮演新增到應用程式上下文的bean工廠。它可能包含帶註釋的工廠方法,@Bean其返回值由Spring自動新增到應用程式上下文中。

簡而言之,Spring配置為應用程式上下文提供bean。

什麼是自動配置?

自動配置是Spring自動發現的@Configuration類。只要該類位於在類路徑classpath上,即可自動配置,並將配置的結果新增到應用程式上下文中。自動配置可以是有條件的,使得其啟用取決於外部因素,例如具有特定值的特定配置引數。

什麼是自動配置模組?

自動配置模組是包含自動配置類的Maven或Gradle模組。這樣,我們就可以構建自動為應用程式上下文做貢獻的模組,新增某個功能或提供對某個外部庫的訪問。我們在Spring Boot應用程式中使用它所要做的就是在我們的pom.xml或者包含它的依賴項build.gradle。

Spring Boot團隊大量使用此方法將Spring Boot與外部庫整合。

什麼是Spring Boot Starter?

最後,Spring Boot Starter是一個Maven或Gradle模組,其唯一目的是提供“使用某個功能”“開始”所需的所有依賴項。這通常意味著它是一個單獨的pom.xml或build.gradle檔案,包含一個或多個自動配置模組的依賴項以及可能需要的任何其他依賴項。

在Spring Boot應用程式中,我們只需要包含此啟動器Starter即可使用該功能。

案例

通過一個例子來了解如何實現一個Spring Boot Starter,想象一下,我們正在微服務環境中工作,並希望實現一個允許服務非同步通訊的啟動器。我們正在建造的Starter將提供以下功能:

  • EventPublisher事件釋出:允許我們將事件傳送到中央訊息傳遞基礎結構的bean
  • 一個抽象EventListener事件監聽類,可以實現從中央訊息傳遞基礎結構訂閱某些事件。

請注意,本文中的實現實際上不會連線到中央訊息傳遞基礎結構,而是提供虛擬實現。本文的目標是展示如何構建Spring Boot Starter,而不是如何進行訊息傳遞。

設定Gradle構建

由於啟動器Starter是跨多個Spring Boot應用程式的交叉問題,它應該存在於自己的程式碼庫中並擁有自己的Maven或Gradle模組。我們將使用Gradle作為選擇的構建工具,但它與Maven非常相似。

我們需要宣告的依賴基本Spring Boot啟動我們的build.gradle檔案:

plugins {
  id 'io.spring.dependency-management' version '1.0.8.RELEASE'
  id 'java'
}

dependencyManagement {
  imports {
    mavenBom("org.springframework.boot:spring-boot-dependencies:2.1.7.RELEASE")
  }
}

dependencies {
  implementation 'org.springframework.boot:spring-boot-starter'
  testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

完整的檔案可以在github上找到

要獲得與某個Spring Boot版本相容的基本啟動程式版本,我們使用Spring Dependency Management外掛來包含該特定版本的BOM(物料清單)。

這樣,Gradle在此BOM中查詢啟動程式的相容版本(以及Spring Boot需要的任何其他依賴項的版本),我們不必手動宣告它。

提供自動配置

提供了一個@Configuration類實現事件釋出者:

@Configuration
class EventAutoConfiguration {

  @Bean
  EventPublisher eventPublisher(List<EventListener> listeners){
    return new EventPublisher(listeners);
  }

}

此配置包括的@Bean是我們提供給Starter所需的所有定義。這裡是需將EventPublisherbean 新增到應用程式上下文中。

EventPublisher需要知道所有的虛擬實現,EventListeners因此它可以將事件傳遞給它們,因此我們讓Spring注入EventListeners應用程式上下文中所有可用的列表。

要使我們的配置成為自動配置,我們將其列在檔案中META-INF/spring.factories:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  io.reflectoring.starter.EventAutoConfiguration

Spring Boot會搜尋spring.factories它在類路徑中找到的所有檔案,並載入在其中宣告的配置。

有了這個EventAutoConfiguration類,我們現在可以為Spring Boot starter自動啟用單點入口。

使其成為可選

允許是否可以禁用Spring Boot starter總是一個好主意。在提供對諸如訊息傳遞服務之類的外部系統的訪問時,這尤其重要。例如,該服務在測試環境中不可用,因此我們希望在測試期間關閉該功能。

我們可以使用Spring Boot的條件註釋使我們的入口點配置成為可選:

@Configuration
@ConditionalOnProperty(value = "eventstarter.enabled", havingValue = "true")
@ConditionalOnClass(name = "io.reflectoring.KafkaConnector")
class EventAutoConfiguration {
  ...
}

通過使用ConditionalOnProperty,我們告訴Spring只在屬性eventstarter.enabled設定為true的情況下將(以及它宣告的所有bean)包含到應用程式上下文中。

@ConditionalOnClass註解告訴Spring:只有在io.reflectoring.KafkaConnector出現類路徑classpath時,才能啟用自動配置(這僅僅是一個虛擬類向人們展示了使用條件註釋)。

使其可配置

對於在多個應用程式中使用的庫,例如我們的啟動程式,將行為設定為儘可能可行是一個好主意。

想象一下,應用程式只對某些事件感興趣。為了使每個應用程式可配置,我們可以在application.yml(或application.properties)檔案中提供已啟用事件的列表:

eventstarter:
  listener:
    enabled-events:
      - foo
      - bar

為了在我們的入門程式碼中輕鬆訪問這些屬性,我們可以提供一個@ConfigurationProperties類

@ConfigurationProperties(prefix = "eventstarter.listener")
@Data
class EventListenerProperties {

  /**
   * List of event types that will be passed to {@link EventListener}
   * implementations. All other events will be ignored.
   */
  private List<String> enabledEvents = Collections.emptyList();

}

通過使用@EnableConfigurationProperties註釋我們的入口點配置來啟用EventListenerProperties類:

@Configuration
@EnableConfigurationProperties(EventListenerProperties.class)
class EventAutoConfiguration {
  ...
}

最後,我們可以讓Spring將EventListenerPropertiesbean 注入我們需要的任何地方,例如在我們的抽象EventListener類中過濾掉我們不感興趣的事件:

@RequiredArgsConstructor
public abstract class EventListener {

  private final EventListenerProperties properties;

  public void receive(Event event) {
    if(isEnabled(event) && isSubscribed(event)){
      onEvent(event);
    }
  }

  private boolean isSubscribed(Event event) {
    return event.getType().equals(getSubscribedEventType());
  }

  private boolean isEnabled(Event event) {
    return properties.getEnabledEvents().contains(event.getType());
  }
}

建立IDE友好的配置後設資料

使用eventstarter.enabled和eventstarter.listener.enabled-events我們為啟動器指定了兩個配置引數。如果這些引數讓開發人員在配置檔案中輸入時自動完成,那將是很好的。

Spring Boot提供了一個註釋處理器,可以從找到的所有@ConfigurationProperties類中收集有關配置引數的後設資料。我們只是將它包含在我們的build.gradle檔案中:

dependencies {
  ...
  annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
}

此註解處理器將生成META-INF/spring-configuration-metadata.json包含有關我們EventListenerProperties類中的配置引數的後設資料的檔案。此後設資料包含欄位上的Javadoc,因此請務必使Javadoc儘可能清晰。

在IntelliJ中,Spring Assistant外掛將讀取此後設資料併為這些屬性提供自動完成功能。

現在還留下eventstarter.enabled問題,它沒有在@ConfigurationProperties的列表中。

我們可以通過建立檔案META-INF/additional-spring-configuration-metadata.json,手動新增此屬性:

{
  "properties": [
    {
      "name": "eventstarter.enabled",
      "type": "java.lang.Boolean",
      "description": "Enables or disables the EventStarter completely."
    }
  ]
}

然後,註釋處理器將自動將此檔案的內容與自動生成的檔案合併,以供IDE工具選取。該檔案的格式記錄在參考手冊中

改善啟動時間

對於類路徑上的每個自動配置類,Spring Boot必須評估@Conditional...註釋中編碼的條件,以決定是否載入自動配置及其所需的所有類。根據Spring Boot應用程式中啟動器的大小和數量,這可能是非常昂貴的操作並影響啟動時間。

還有另一個註釋處理器可生成有關所有自動配置條件的後設資料。Spring Boot在啟動期間讀取此後設資料,並且可以過濾掉不滿足條件的配置,而無需實際檢查這些類。

要生成此後設資料,我們只需將註釋處理器新增到啟動器模組:

dependencies {
    ...
    annotationProcessor 'org.springframework.boot:spring-boot-autoconfigure-processor'
}

在構建期間,後設資料將生成到META-INF/spring-autoconfigure-metadata.properties檔案中,如下所示:

io.reflectoring.starter.EventAutoConfiguration=
io.reflectoring.starter.EventAutoConfiguration.ConditionalOnClass=io.reflectoring.KafkaConnector
io.reflectoring.starter.EventAutoConfiguration.Configuration=

我不確定為什麼後設資料包含@ConditionalOnClass條件但不包含@ConditionalOnProperty條件。如果您知道原因,請在評論中告訴我。

使用入門

現在啟動器已經完成,它已準備好,可以包含在Spring Boot應用程式中了。

在build.gradle檔案中新增單個依賴項即可實現包含它:

dependencies {
  ...
  implementation project(':event-starter')
}

在上面的示例中,starter是同一Gradle構建中的模組,因此我們不必像在完全限定的Maven中需要使用來定位標識starter。

我們現在可以使用上面介紹的配置引數配置啟動器。希望我們的IDE能夠評估我們建立的配置後設資料,併為我們自動完成引數名稱。

要使用我們的事件啟動器,我們現在可以EventPublisher在bean中注入並使用它來發布事件。此外,我們可以建立擴充套件EventListener類的bean 來接收和處理事件。

GitHub上提供一個工作示例應用程式。

相關文章