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 進行配置的提示,也可以用來完成配置的遷移。非常的簡單。
相關文章: SpringBoot 配置提示功能
通過閱讀代碼,獲得以下信息:
- 監聽
ApplicationPreparedEvent
事件(即:環境已准備事件),執行以下操作並收集信息 - 從
classpath*:/META-INF/spring-configuration-metadata.json
中載入所有配置 - 從上下文的
environment
中過濾出提示的配置(滿足條件:1. deprecation 不為 null,且提示level
為 error) - 判斷是否兼容(兼容條件見下一節),提取出兼容的屬性
- 將 value 對應到
replacement
的 key,並將其屬性源命名為:migrate-原名 - 將配置遷移的
新屬性源
添加到 environment 中,且添加
到原屬性源之前
(優先級高)。 - 監聽事件:ApplicationReadyEvent(應用上下文已准備) 或 ApplicationFailedEvent(應用啟動失敗),打印以上步驟收集的遺留配置信息。以 warn 級別打印兼容的配置,以 error 級別打印不兼容的配置
配置兼容條件
根據元數據中定義的 type
判斷
- 如果舊類型、新類型其中之一為 null(元數據中未指定),則不兼容
- 如果兩個類型一樣,兼容
- 如果新類型是 Duration,而舊類型是 Long 或 Integer,則兼容
- 其他情況視為不兼容
- 從
environment
中取配置信息,理論上支持 SpringBoot 所有的配置方式。
效果
兼容效果:
棄用
屬性(如果還存在)與替換
后的屬性都會使用配置文件中的棄用的屬性名所對應的的值。
總結
使用配置遷移功能,需要以下步驟:
- 引入依賴:
spring-boot-properties-migrator
(支持配置遷移)、spring-boot-configuration-processor
(生成元數據文件,如果已經有完整的,不需要此依賴) - 元數據文件
spring-configuration-metadata.json
中棄用屬性名對應的 properties 中必須有deprecation
(在additional-spring-configuration-metadata.json
中添加,相關文章: SpringBoot 配置提示功能 ) deprecation
中需指定level
為 errordeprecation
中需指定replacement
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
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 領域知識的深入學習,從源碼到原理,系統有序的學習)