1. 程式人生 > >深入Spring Boot:編寫相容Spring Boot1和Spring Boot2的Starter

深入Spring Boot:編寫相容Spring Boot1和Spring Boot2的Starter

前言

Spring Boot 2正式釋出已經有段時間,應用升級之前,starter先要升級,那麼如何支援Spring Boot 2?

為什麼選擇starter同時相容spring boot 1和spring boot 2

  • 從使用者角度來看

    如果不在一個starter裡相容,比如用版本號來區分,spring boot 1的使用者使用1.*,spring boot 2使用者使用2.*,這樣使用者升級會有很大困擾。

    另外,我們的starter是以日期為版本號的,如果再分化,則就會出現2018-06-stable-boot12018-06-stable-boot2,這樣子很醜陋。

  • 從開發者角度來看

    要同時維護兩個分支,修改程式碼時要合到兩個分支上,發版本時要同時兩個。如果有統一的bom檔案,也需要維護兩份。工作量翻倍,而且很容易出錯。

因此,我們決定在同一個程式碼分支裡,同時支援spring boot 1/2。減少開發維護成本,減少使用者使用困擾。

編寫相容的starter的難點

spring boot starter的程式碼入口都是在各種@Configuration類裡,這為我們編寫相容starter提供了條件。

但還是有一些難點:

  • 某些類不相容,比如在spring boot 2裡刪除掉了
  • 程式碼模組,maven依賴怎樣組織
  • 怎樣保證starter在spring boot 1/2裡都能正常工作

通過ASM分析現有的starter裡不相容的類

springboot-classchecker可以從jar包裡掃描出哪些類在spring boot 2裡不存在的。

工作原理:springboot-classchecker自身在pom.xml裡依賴的是spring boot 2,掃描jar包裡通過ASM分析到所有的String,提取出類名之後,再嘗試在ClassLoader里加載,如果載入不到,則說明這個類在spring boot 2裡不存在。

例如掃描demo-springboot1-starter.jar

mvn clean package
java -jar target/classchecker-0.0
.1-SNAPSHOT.jar demo-springboot1-starter.jar

結果是:

path: demo-springboot1-starter.jar
org.springframework.boot.actuate.autoconfigure.ConditionalOnEnabledHealthIndicator
org.springframework.boot.actuate.autoconfigure.EndpointAutoConfiguration
org.springframework.boot.actuate.autoconfigure.HealthIndicatorAutoConfiguration

那麼這些類在spring boot 2在哪裡了?

實際上是改了package:

org.springframework.boot.actuate.autoconfigure.health.ConditionalOnEnabledHealthIndicator
org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration
org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration

通過掃描20多個starter jar包,發現不相容的類有:

  • org.springframework.boot.env.PropertySourcesLoader
  • org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder
  • org.springframework.boot.bind.RelaxedDataBinder
  • Endpoint/HealthIndicator 相關的類

可以總結:

  • spring boot核心的類,autoconfigure相關的沒有改動
  • 大部分修改的是Endpoint/HealthIndicator 相關的類

spring-boot-utils相容工具類

spring-boot-utils提供相容工具類,同時支援spring boot 1/2。

BinderUtils

在spring boot 1裡,注入環境變數有時需要用到RelaxedDataBinder

MyProperties myProperties = new MyProperties();
MutablePropertySources propertySources = environment.getPropertySources();
new RelaxedDataBinder(myProperties, "spring.my").bind(new PropertySourcesPropertyValues(propertySources));

在spring boot 2裡,RelaxedDataBinder刪除掉了,新的寫法是用Binder

Binder binder = Binder.get(environment);
MyProperties myProperties = binder.bind("spring.my", MyProperties.class).get();

通過BinderUtils,則可以同時支援spring boot1/2:

MyProperties myProperties = BinderUtils.bind(environment, "spring.my", MyProperties.class);

@ConditionalOnSpringBoot1/@ConditionalOnSpringBoot2

spring boot starter的功能大部分都是通過@Configuration組裝起來的。spring boot 1的Configuration類,不能在spring boot 2裡啟用。則可以通過@ConditionalOnSpringBoot1@ConditionalOnSpringBoot2這兩個註解來分別支援。

其實原理很簡單,判斷spring boot 1/2裡各自有的存在的類就可以了。

@ConditionalOnClass(name = "org.springframework.boot.bind.RelaxedDataBinder")
public @interface ConditionalOnSpringBoot1 {
}
@ConditionalOnClass(name = "org.springframework.boot.context.properties.bind.Binder")
public @interface ConditionalOnSpringBoot2 {
}

Starter程式碼模組組織

下面以實際的一個starter來說明。

spring boot web應用的mappings資訊,可以在/mappings endpoint查詢到。但是這麼多endpoint,它們都提供了哪些url?

endpoints-spring-boot-starter的功能是展示所有endpoints的url mappings資訊

endpoints-spring-boot-starter裡需要給spring boot 1/2同時提供endpoint功能,程式碼模組如下:

endpoints-spring-boot-starter
|__ endpoints-spring-boot-autoconfigure1
|__ endpoints-spring-boot-autoconfigure2
  • endpoints-spring-boot-autoconfigure1模組在pom.xml裡依賴的是spring boot 1相關的jar,並且都設定為<optional>true</optional>
  • endpoints-spring-boot-autoconfigure2的配置類似
  • endpoints-spring-boot-starter依賴autoconfigure1 和 autoconfigure2
  • 如果有公共的邏輯,可以增加一個commons模組

Endpoint相容

以 endpoints-spring-boot-autoconfigure1模組為例說明怎樣處理。

  • EndPointsEndPoint類繼承自spring boot 1的AbstractMvcEndpoint

    @ConfigurationProperties("endpoints.endpoints")
    public class EndPointsEndPoint extends AbstractMvcEndpoint {
  • 通過@ManagementContextConfiguration引入

    @ManagementContextConfiguration
    public class EndPointsEndPointManagementContextConfiguration {
    
      @Bean
      @ConditionalOnMissingBean
      @ConditionalOnEnabledEndpoint("endpoints")
      public EndPointsEndPoint EndPointsEndPoint() {
          EndPointsEndPoint endPointsEndPoint = new EndPointsEndPoint();
          return endPointsEndPoint;
      }
    
    }
  • META-INF/resources/spring.factories裡配置

    org.springframework.boot.actuate.autoconfigure.ManagementContextConfiguration=\
    io.github.hengyunabc.endpoints.autoconfigure1.EndPointsEndPointManagementContextConfiguration

因為org.springframework.boot.actuate.autoconfigure.ManagementContextConfiguration是隻在spring boot 1裡,在spring boot 2的應用裡不會載入它,所以autoconfigure1模組天然相容spring boot 2。

那麼類似的,autoconfigure2模組裡在META-INF/resources/spring.factories配置的是

org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration=\
io.github.hengyunabc.endpoints.autoconfigure2.ManagementApplicationcontextHolderConfiguration

仔細對比,可以發現是spring boot 2下面修改了ManagementContextConfiguration的包名,所以對於Endpoint天然是相容的,不同的模組自己編繹就可以了。

HealthIndicator的相容

類似Endpoint的處理,spring boot 1/2的程式碼分別放不同的autoconfigure模組裡,然後各自的@Configuration類分別使用@ConditionalOnSpringBoot1/@ConditionalOnSpringBoot2來判斷。

通過整合測試保證相容性

還是以endpoints-spring-boot-autoconfigure1模組為例。

這個模組是為spring boot 1準備的,則它的整合測試要配置為spring boot 2。

參考相關的程式碼:檢視

  • springboot2demo/pom.xml裡依賴spring boot 2
  • verify.groovy裡檢測應用是否啟動成功

總結

  • 通過ASM分析現有的starter裡不相容的類
  • 配置注入通過BinderUtils解決
  • 各自的@Configuration類分別用@ConditionalOnSpringBoot1/@ConditionalOnSpringBoot2來判斷
  • 程式碼分模組:commons放公共邏輯, autoconfigure1/autoconfigure2 對應 spring boot 1/2的自動裝配,starter給應用依賴
  • Endpoint的Configuration入口是ManagementContextConfiguration,因為spring boot 2裡修改了package,所以直接在spring.factories裡配置即可
  • 通過整合測試保證相容性
  • 如果某一天,不再需要支援spring boot 1了,則直接把autoconfigure1模組去掉即可

連結