Полиморфные свойства конфигурации в Spring Boot
Я хотел бы использовать полиморфные свойства конфигурации в Spring, используя Spring @ConfigurationProperties
аннотаций.
Предположим, у нас есть следующие классы POJO.
public class Base {
private String sharedProperty;
public String getSharedProperty() {
return sharedProperty;
}
public String setSharedProperty(String sharedProperty) {
this.sharedProperty = sharedProperty;
}
}
public class Foo extends Base {
private String fooProperty;
public String getFooProperty() {
return fooProperty;
}
public String setFooProperty(String sharedProperty) {
this. fooProperty = fooProperty;
}
}
public class Bar extends Base {
private String barProperty;
public String getSharedProperty() {
return sharedProperty;
}
public String setBarProperty(String barProperty) {
this.barProperty = barProperty;
}
}
И класс свойств конфигурации,
@Component
@ConfigurationProperties(prefix = "playground")
public class SomeConfigurationProperties {
private List<Base> mixed;
public List<Base> getMixed() {
return mixed;
}
public void setMixed(List<Base> mixed) {
this.mixed = mixed;
}
}
И application.yml
файл,
playground:
mixed:
- shared-property: "shared prop"
foo-property: "foo prop"
- shared-property: "shared prop"
bar-property: "bar prop"
Однако в этой конфигурации Spring инициализирует @ConfigurationProperties
-аннотированный класс со списком Base
объекты, а не их подклассы. Это, на самом деле, ожидаемое поведение (из соображений безопасности).
Есть ли способ заставить поведение SnakeYAML использовать подклассы или реализовать какой-либо пользовательский поставщик десериализации?
1 ответ
Хотя можно реализовать пользовательские PropertySources и / или ConversionService, пользовательский поставщик десериализации не требуется.
У Spring нет проблем с привязкой одинаковых свойств к нескольким bean-компонентам. Причина, по которой ваша реализация не работает, заключается в том, что вы регистрируете только один компонент в ApplicationContext с помощью @Component
аннотация на базовый класс. Это говорит о том, что компонентный сканер имеет только один тип синглтона. Base
, Так как Foo
а также Bar
не зарегистрированы как бобы, они не будут связаны с.
Если единственная причина, по которой вы пытаетесь сделать эти полиморфные, заключается в том, чтобы делиться префиксами имен свойств в конфигурации на основе SnakeYAML, то вам фактически не нужно вводить полиморфные отношения, и вы можете связывать общие свойства по общему имени поля в разных классах.
Есть много способов реализовать то, о чем вы просите, однако полиморфным способом, вот некоторые из самых простых простых:
Самостоятельная декларация Полиморфная конфигурация
Вместо применения @ConfigurationProperties
а также @Component
аннотации к базовому классу, применять их к конкретным классам с одинаковым префиксом имени свойства. Это не будет моим предпочтительным подходом, поскольку каждый компонент не будет зависеть от того, какие свойства установлены, однако он может удовлетворить ваши потребности. В зависимости от того, позволяет ли ваша конфигурация Spring перезагружать свойства, Spring будет поддерживать привязки для всех bean-компонентов.
Примечание. Начиная с IntelliJ Idea 2018.3, был добавлен профиль проверки, чтобы идентифицировать дублирующиеся префиксные ключи как ошибку. Вы можете игнорировать это или подавить предупреждения.
Я успешно проверил следующее:
Base.java
package sample;
public class Base {
private String sharedProperty;
public String getSharedProperty() {
return sharedProperty;
}
public void setSharedProperty(String sharedProperty) {
this.sharedProperty = sharedProperty;
}
}
Foo.java
package sample;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties("playground")
public class Foo extends Base {
private String fooProperty;
public String getFooProperty() {
return fooProperty;
}
public void setFooProperty(String fooProperty) {
this.fooProperty = fooProperty;
}
}
Bar.java
package sample;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties("playground")
public class Bar extends Base {
private String barProperty;
public String getBarProperty() {
return barProperty;
}
public void setBarProperty(String barProperty) {
this.barProperty = barProperty;
}
}
application.yml
playground:
shared-property: "shared prop"
foo-property: "foo prop"
bar-property: "bar prop"
SampleAppTest.java
package sample;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.core.env.Environment;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
@SpringBootTest
public class SampleAppTest {
@Autowired
public Environment environment;
@Test
public void test(@Autowired Bar bar, @Autowired Foo foo) {
assertEquals("shared prop", bar.getSharedProperty());
assertEquals("shared prop", foo.getSharedProperty());
assertEquals("bar prop", bar.getBarProperty());
assertEquals("foo prop", foo.getFooProperty());
}
@Test
public void testSuper(@Autowired List<Base> props) {
assertEquals(2, props.size());
}
}
Полиморфная конфигурация свойств бобов зависит от свойств
Возможно, вы не захотите создавать экземпляры определенных конкретных реализаций, если отсутствуют их конкретные свойства. Кроме того, вы можете не захотеть соединить @ConfigurationProperties
а также @Component
аннотации к каждому конкретному классу. Эта реализация создает компоненты ConfigurationProperties через Spring @Configuration
боб. Компонент конфигурации гарантирует, что они построены только условно с помощью проверки существования свойства. Эта реализация также создает компонент конкретного типа Base
если ни один из других Base
бобы соответствуют условиям, и общие свойства существуют. Здесь используется тот же модульный тест из предыдущего примера, который проходит:
Base.java
package sample;
public class Base {
private String sharedProperty;
public String getSharedProperty() {
return sharedProperty;
}
public void setSharedProperty(String sharedProperty) {
this.sharedProperty = sharedProperty;
}
}
Foo.java
package sample;
public class Foo extends Base {
private String fooProperty;
public String getFooProperty() {
return fooProperty;
}
public void setFooProperty(String fooProperty) {
this.fooProperty = fooProperty;
}
}
Bar.java
package sample;
public class Bar extends Base {
private String barProperty;
public String getBarProperty() {
return barProperty;
}
public void setBarProperty(String barProperty) {
this.barProperty = barProperty;
}
}
SampleConfiguration.java
package sample;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class SampleConfiguration {
@Bean
@ConfigurationProperties("playground")
@ConditionalOnProperty("playground.foo-property")
public Foo foo() {
return new Foo();
}
@Bean
@ConfigurationProperties("playground")
@ConditionalOnProperty("playground.bar-property")
public Bar bar() {
return new Bar();
}
@Bean
@ConfigurationProperties("playground")
@ConditionalOnProperty("playground.shared-property")
@ConditionalOnMissingBean(Base.class)
public Base base() {
return new Base();
}
}
application.yml
playground:
shared-property: "shared prop"
foo-property: "foo prop"
bar-property: "bar prop"
SampleAppTest.java
package sample;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.core.env.Environment;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
@SpringBootTest
public class SampleAppTest {
@Autowired
public Environment environment;
@Test
public void test(@Autowired Bar bar, @Autowired Foo foo) {
assertEquals("shared prop", bar.getSharedProperty());
assertEquals("shared prop", foo.getSharedProperty());
assertEquals("bar prop", bar.getBarProperty());
assertEquals("foo prop", foo.getFooProperty());
}
@Test
public void testSuper(@Autowired List<Base> props) {
assertEquals(2, props.size());
}
}