天啦!竟然从来没有人讲过 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 进行配置的提示,也可以用来完成配置的迁移。非常的简单。
通过阅读代码,获得以下信息:
监听
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
中添加)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 中弃用属性名优先级更高,弃用属性与新属性都使用此弃用属性名对应的值。