美文网首页
天啦!竟然从来没有人讲过 SpringBoot 支持配置如此平滑

天啦!竟然从来没有人讲过 SpringBoot 支持配置如此平滑

作者: 逸飞兮 | 来源:发表于2019-11-26 09:05 被阅读0次
    候鸟迁徙

    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 配置提示功能

    通过阅读代码,获得以下信息:

    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 中添加,相关文章: 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

    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
    ......... myPropertiesapp.name:lw ......... serverPropertiesservlet.contextPath:/app

    7、效果解析
    在 yml 中弃用属性名优先级更高,弃用属性与新属性都使用此弃用属性名对应的值。

    参考资料

    SpringBoot 2.2.1.RELEASE 源码
    公众号:逸飞兮(专注于 Java 领域知识的深入学习,从源码到原理,系统有序的学习)

    逸飞兮

    相关文章

      网友评论

          本文标题:天啦!竟然从来没有人讲过 SpringBoot 支持配置如此平滑

          本文链接:https://www.haomeiwen.com/subject/oczswctx.html