天啦!竟然从来没有人讲过 SpringBoot 支持配置如此平滑的迁移 候鸟迁徙
SpringBoot 是原生支持配置迁移的,但是官方文档没有看到这方面描述,在源码中才看到此模块,spring-boot-properties-migrator,幸亏我没有跳过。看到这篇文章的各位,可算是捡到宝了,相信你继续往下看下去,定会忍不住点赞、收藏、关注。
效果
先放个效果吸引你 😃
从 SpringBoot 2.0.0 版本开始,配置服务上下文,不支持 server.context-path,而需要server.servlet.context-path配置。但是只要加上以下一个官方依赖,就可以支持使用 server.context-path
org.springframework.boot
spring-boot-properties-migrator
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 为 error
deprecation 中需指定 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、引入依赖
org.springframework.boot
spring-boot-properties-migrator
org.springframework.boot
spring-boot-configuration-processor
true
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
......... myPropertiesapp.name:lw.........serverPropertiesservlet.contextPath:/app
7、效果解析 在 yml 中弃用属性名优先级更高,弃用属性与新属性都使用此弃用属性名对应的值。
参考资料
SpringBoot 2.2.1.RELEASE 源码 公众号:逸飞兮(专注于 Java 领域知识的深入学习,从源码到原理,系统有序的学习)https://www.cnblogs.com/lw5946/p/11933190.html