更改 Spring 引导外部化配置时的向后兼容性

Backwards compatibility when changing Spring Boot externalized configuration

是否有推荐的方法将 restructurings/renamings 引入外部化配置,同时为仍然依赖旧配置结构的消费者保持向后兼容性?

例如,给定一个库过去使用了通过 @ConfigurationProperties 定义的以下配置结构:

old-properties:
  an:
    old-property: true
  another:
    custom-property: 1234

该库的新版本将配置重新定义为如下内容:

my-library:
  a-property: true
  another-property: 1234

是否有一种好的方法可以在一段时间内保持对现有消费者的兼容性,同时弃用旧结构?使用新版本库的消费者应该仍然可以使用 old-properties.an.old-property 并自动映射到 my-library.a-property.

我知道使用 additional configuration metadata 将 属性 标记为已弃用的功能,但我正在明确寻找一种方法来支持这两个版本以简化迁移。

Spring 引导配置处理器为此提供了一个 @DeprecatedConfigurationProperty 注释。生成的元数据文件将包含任何 reason/replacement 注释,如果使用带注释的 属性,则会导致记录适当的弃用警告。

参见 here for a basic example. The following snippet from CassandraProperties.java 展示了一个真实世界的用例,其中 spring.data.cassandra.cluster-name 被弃用,取而代之的是 spring.data.cassandra.session-name。向后兼容性是通过简单地调用 getter/setter 来替换 属性 在 getter/setter 中为已弃用的 属性:

public String getSessionName() {
    return this.sessionName;
}

public void setSessionName(String sessionName) {
    this.sessionName = sessionName;
}

@Deprecated
@DeprecatedConfigurationProperty(replacement = "spring.data.cassandra.session-name")
public String getClusterName() {
    return getSessionName();
}

@Deprecated
public void setClusterName(String clusterName) {
    setSessionName(clusterName);
}

要为未映射到 @ConfigurationProperties bean 的属性实现相同的行为,您可以在 META-INF/additional-spring-configuration-metadata.json 中手动指定它们并添加对 org.springframework.boot:spring-boot-properties-migrator 的运行时依赖性。请参阅 Spring Boot docs 以供参考。

以下摘自 spring-boot-autoconfigure shows a real-world use case in which server.servlet.path was deprecated in favor of spring.mvc.servlet.path. Backwards compatibility is handled by the PropertiesMigrationListener 的代码片段,其中 “自动重命名具有匹配替换项的键并记录[s] 所发现内容的报告。”

    {
      "name": "server.servlet.path",
      "type": "java.lang.String",
      "description": "Path of the main dispatcher servlet.",
      "defaultValue": "/",
      "deprecation": {
        "replacement": "spring.mvc.servlet.path",
        "level": "error"
      }
    },

如果您设置已弃用的 属性 server.servlet.path=/foo,替换 属性 @Value("${spring.mvc.servlet.path}") 将评估为 /foo,并且将记录弃用通知启动。

我研究了 Spring Boot 如何处理 deprecation phase for logging.file(被 logging.file.name 取代),当他们直接在代码中实现回退时,我决定通过创建来尝试类似的东西@Bean 方法中的新 @ConfigurationProperties 处理从旧 属性 名称设置值(如果可用)。

鉴于新的配置结构如下所示(为简洁起见,使用 Lombok):

import lombok.Data;

@Data
public class MyLibraryConfigurationProperties {

    private String aProperty;
    private String anotherProperty;

}

@Bean 方法现在负责读取旧值并将其应用于属性:

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;

@Configuration
public class MyLibraryConfiguration {

    @Bean
    @ConfigurationProperties(prefix = "my-library")
    public MyLibraryConfigurationProperties myLibraryConfigurationProperties(Environment environment) {
        MyLibraryConfigurationProperties config = new MyLibraryConfigurationProperties();

        // fallback to old property if available
        if (environment.containsProperty("old-properties.an.old-property")) {
            // here we could also log warnings regarding the deprecation
            config.setAProperty(environment.getProperty("old-properties.an.old-property"));
        }

        return config;
    }
}

如果新值也通过配置设置,它将覆盖旧 属性 设置的值。