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

候鳥遷徙

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

效果

先放個效果吸引你 :)git

從 SpringBoot 2.0.0 版本開始,配置服務上下文,不支持 server.context-path,而須要server.servlet.context-path配置。可是隻要加上如下一個官方依賴,就能夠支持使用 server.context-pathweb

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

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

原理

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

相關文章: SpringBoot 配置提示功能app

經過閱讀代碼,得到如下信息:spring-boot

  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 全部的配置方式

效果

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

總結

使用配置遷移功能,須要如下步驟:spa

  1. 引入依賴:spring-boot-properties-migrator(支持配置遷移)、spring-boot-configuration-processor(生成元數據文件,若是已經有完整的,不須要此依賴)
  2. 元數據文件spring-configuration-metadata.json 中棄用屬性名對應的 properties 中必須有 deprecation(在additional-spring-configuration-metadata.json 中添加,相關文章: SpringBoot 配置提示功能
  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 的屬性纔有。

可是其知足配置兼容的條件,爲何實際上使用卻好像不兼容呢?
實際上是由於沒有引入依賴,當引入依賴,就會發現此方式配置能夠起做用。

示例之兩種屬性都存在

代碼示例見 https://gitee.com/lw888/spring-boot-source-example/tree/master/properties-migrator

一、引入依賴

<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>

二、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;
  }
}

三、元數據配置,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."
    }
  ]
}

四、在 properties 或 yml 文件中配置

my:
  name: lw
  app:
    name: app

五、打印配置信息

@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());
  }
}

六、打印信息以下:

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

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

參考資料

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

逸飛兮

相關文章
相關標籤/搜索