天啦!竟然從來沒有人講過 SpringBoot 支持配置如此平滑的遷移

SpringBoot 是原生支持配置遷移的,但是官方文檔沒有看到這方面描述,在源碼中才看到此模塊,spring-boot-properties-migrator,幸虧我沒有跳過。看到這篇文章的各位,可算是撿到寶了,相信你繼續往下看下去,定會忍不住點贊、收藏、關注。

效果

先放個效果吸引你 🙂

從 SpringBoot 2.0.0 版本開始,配置服務上下文,不支持 server.context-path,而需要server.servlet.context-path配置。但是只要加上以下一個官方依賴,就可以支持使用 server.context-path

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-properties-migrator</artifactId>
    </dependency>

server.context-path 所對應的屬性 ServerProperties#contextPath 在 Java 代碼中已不存在,server.servlet.context-path 所對應的的屬性在內部類 Servlet 中才有,為何加了此依賴就能實現如此神奇的效果呢。

原理

SpringBoot 對外部化配置原生支持遷移功能,所謂遷移,具體是指對應配置的屬性名變動,仍可以使用原來的屬性名配置。
spring-configuration-metadata.json 的信息可以輔助 IDE 進行配置的提示,也可以用來完成配置的遷移。非常的簡單。

相關文章:

通過閱讀代碼,獲得以下信息:

  1. 監聽 ApplicationPreparedEvent 事件(即:環境已準備事件),執行以下操作並收集信息
  2. classpath*:/META-INF/spring-configuration-metadata.json 中載入所有配置
  3. 從上下文的 environment 中過濾出提示的配置(滿足條件:1. deprecation 不為 null,且提示 level 為 error)
  4. 判斷是否兼容(兼容條件見下一節),提取出兼容的屬性
  5. 將 value 對應到 replacement 的 key,並將其屬性源命名為:migrate-原名
  6. 將配置遷移的新屬性源添加到 environment 中,且添加到原屬性源之前(優先級高)。
  7. 監聽事件:ApplicationReadyEvent(應用上下文已準備) 或 ApplicationFailedEvent(應用啟動失敗),打印以上步驟收集的遺留配置信息。以 warn 級別打印兼容的配置,以 error 級別打印不兼容的配置

配置兼容條件

根據元數據中定義的 type 判斷

  1. 如果舊類型、新類型其中之一為 null(元數據中未指定),則不兼容
  2. 如果兩個類型一樣,兼容
  3. 如果新類型是 Duration,而舊類型是 Long 或 Integer,則兼容
  4. 其他情況視為不兼容
  5. environment 中取配置信息,理論上支持 SpringBoot 所有的配置方式

效果

兼容效果:
棄用屬性(如果還存在)與替換后的屬性都會使用配置文件中的棄用的屬性名所對應的的值。

總結

使用配置遷移功能,需要以下步驟:

  1. 引入依賴:spring-boot-properties-migrator(支持配置遷移)、spring-boot-configuration-processor(生成元數據文件,如果已經有完整的,不需要此依賴)
  2. 元數據文件spring-configuration-metadata.json 中棄用屬性名對應的 properties 中必須有 deprecation(在additional-spring-configuration-metadata.json 中添加,相關文章: )
  3. deprecation 中需指定 levelerror
  4. deprecation 中需指定 replacement
  5. replacement 對應的屬性配置在元數據文件中存在,與棄用屬性兼容

經典示例之配置上下文

再說回一開始展示的配置上下文示例。

# 配置 servlet 服務上下文
server:
  context-path: test

從 SpringBoot 2.0.0 版本開始,以上配置不支持,點到配置元數據文件中(spring-configuration-metadata.json),發現如下信息:

{
  "properties": [
    {
      "name": "server.context-path",
      "type": "java.lang.String",
      "description": "Context path of the application.",
      "deprecated": true,
      "deprecation": {
        "level": "error",
        "replacement": "server.servlet.context-path"
      }
    },
    {
      "name": "server.servlet.context-path",
      "type": "java.lang.String",
      "description": "Context path of the application.",
      "sourceType": "org.springframework.boot.autoconfigure.web.ServerProperties$Servlet"
    }

替換屬性名為:server.servlet.context-path,此屬性在org.springframework.boot.autoconfigure.web.ServerProperties 中,且在類中可以發現,server.context-path 所對應的屬性 ServerProperties#contextPath 在代碼中已不存在,而是在內部類 Servlet 中有,也就是對應 server.servlet.context-path 的屬性才有。

但是其滿足配置兼容的條件,為什麼實際上使用卻好像不兼容呢?
其實是因為沒有引入依賴,當引入依賴,就會發現此方式配置可以起作用。

示例之兩種屬性都存在

代碼示例見

1、引入依賴

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-properties-migrator</artifactId>
</dependency>

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-configuration-processor</artifactId>
  <optional>true</optional>
</dependency>

2、Java 配置
此處故意保留棄用屬性

@Data
@Configuration
@ConfigurationProperties(prefix = "my")
public class MyProperties {
  /** the project name */
  private String name;

  private App app;

  @Data
  public static class App {
    private String name;
  }
}

3、元數據配置,spring-configuration-metadata.json 由程序生成,自定義配置放在 additional-spring-configuration-metadata.json

{
  "properties": [
    {
      "name": "my.name",
      "type": "java.lang.String",
      "description": "the project name.",
      "deprecation": {
        "reason": "test the properties-migrator feature.",
        "replacement": "my.app.name",
        "level": "error"
      }
    },
    {
      "name": "my.app.name",
      "type": "java.lang.String",
      "sourceType": "com.lw.properties.migrator.config.MyProperties$App",
      "description": "the project name."
    }
  ]
}

4、在 properties 或 yml 文件中配置

my:
  name: lw
  app:
    name: app

5、打印配置信息

@Slf4j
@SpringBootApplication
public class PropertiesMigratorApplication {

  public static void main(String[] args) {
    ConfigurableApplicationContext context =
        SpringApplication.run(PropertiesMigratorApplication.class, args);
    MyProperties myProperties = context.getBean(MyProperties.class);
    log.info("myProperties.name:{}", myProperties.getName());
    log.info(
        "myProperties$app.name:{}",
        Optional.ofNullable(myProperties.getApp()).orElse(new App()).getName());
  }
}

6、打印信息如下:

2019-11-23 21:42:09.580 WARN 109408 — [ main] o.s.b.c.p.m.PropertiesMigrationListener :
The use of configuration keys that have been renamed was found in the environment:

Property source ‘applicationConfig: [classpath:/application.yml]’:
Key: my.name
Line: 4
Replacement: my.app.name
Key: server.context-path
Line: 2
Replacement: server.servlet.context-path

Each configuration key has been temporarily mapped to its replacement for your convenience. To silence this warning, please update your configuration to use the new keys.
……… myProperties.name:lw
……… myProperties\(app.name:lw ……… serverProperties\)servlet.contextPath:/app

7、效果解析
在 yml 中棄用屬性名優先級更高,棄用屬性與新屬性都使用此棄用屬性名對應的值。

參考資料

SpringBoot 2.2.1.RELEASE 源碼
公眾號:逸飛兮(專註於 Java 領域知識的深入學習,從源碼到原理,系統有序的學習)

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

您可能也會喜歡…