Существует ли рекомендуемый способ внесения реструктуризации/переименования во внешнюю конфигурацию с сохранением обратной совместимости для потребителей, которые все еще полагаются на старую структуру конфигурации?
Например, если библиотека использовала следующую структуру конфигурации, определенную через @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.
Мне известны возможности использования дополнительных метаданных конфигурации для пометки свойства как устаревшего, но я явно ищу способ поддержки обеих версий для упрощения миграции.
Обработчик конфигурации Spring Boot предоставляет для этой цели аннотацию @DeprecatedConfigurationProperty. Сгенерированный файл метаданных будет включать любые примечания о причинах/заменах, что приводит к регистрации соответствующих предупреждений об устаревании, если используется аннотированное свойство.
Смотрите здесь для основного примера. Следующий фрагмент из CassandraProperties.java показывает реальный вариант использования, в котором spring.data.cassandra.cluster-name устарела в пользу spring.data.cassandra.session-name. Обратная совместимость обеспечивается простым вызовом геттера/сеттера для свойства замены в геттере/сеттере для устаревшего свойства:
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);
}
Чтобы добиться того же поведения для свойств, которые не сопоставлены с bean-компонентом @ConfigurationProperties, вы можете вручную указать их в META-INF/additional-spring-configuration-metadata.json и добавить зависимость времени выполнения от org.springframework.boot:spring-boot-properties-migrator. См. Spring Boot docs для справки.
Следующий фрагмент из spring-boot-autoconfigure показывает реальный вариант использования, в котором server.servlet.path устарела в пользу spring.mvc.servlet.path. Обратная совместимость обеспечивается PropertiesMigrationListener, который «автоматически переименовывает ключи, имеющие совпадающую замену, и регистрирует отчет о том, что было обнаружено»:
{
"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 обрабатывал фазу устаревания для файла logging.file (который был заменен на logging.file.name), и поскольку они реализовали откат непосредственно в коде, я решил попробовать что-то подобное, создав новый @ConfigurationProperties в методе @Bean, который обрабатывает установка значений из старых имен свойств, если они доступны.
Учитывая, что новая структура конфигурации выглядит следующим образом (для краткости используется 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;
}
}
Если новое значение также установлено через конфигурацию, оно переопределит значение, установленное из старого свойства.
Я должен был упомянуть, что я знаю (и уже использую) дополнительные метаданные, чтобы пометить эти свойства как устаревшие, и что я беспокоюсь только о поддержке обеих версий в течение некоторого времени, чтобы упростить миграцию. Спасибо за подсказку с @DeprecatedConfigurationProperty, я знал только про additional-spring-configuration-metadata.json способ.