问题描述
我想在 Spring 上使用多态配置属性,使用 Spring 的 @ConfigurationProperties
注释.
I would like to use polymorphic configuration properties on Spring, using Spring's @ConfigurationProperties
annotation.
假设我们有以下 POJO 类.
Suppose we have the following POJO classes.
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 使用 Base
对象列表而不是它们的子类来初始化带有 @ConfigurationProperties
注释的类.这实际上是一种预期行为(出于安全考虑).
However, with this configuration, Spring initializes the @ConfigurationProperties
-annotated class with the list of Base
objects, instead of their subclasses. That is, actually, an expected behavior (due to security concerns).
有没有办法强制 SnakeYAML 的行为使用子类,或实现任何类型的自定义反序列化提供程序?
Is there a way to enforce the behavior of SnakeYAML to use subclasses, or implement any kind of custom deserialization provider?
推荐答案
虽然可以实现自定义 PropertySources 和/或 ConversionService,不需要自定义反序列化提供程序.
Although it is possible to implement custom PropertySources and/or ConversionService, a custom deserialization provider is not necessary.
Spring 将相同的属性绑定到多个 bean 没有问题.您的实现无法正常工作的原因是您只在 ApplicationContext 中注册了一个 bean,并在基类上使用了 @Component
注释.这告诉组件扫描器只有一个 Base
类型的单例.因为 Foo
和 Bar
没有注册为 bean,所以它们不会被绑定.
Spring has no issues binding the same properties to multiple beans. The reason your implementation is not working is because you are only registering one bean with the ApplicationContext with the @Component
annotation on the base class. This is telling the component scanner that there is only one singleton of type Base
. Because Foo
and Bar
are not registered as beans, they won't be bound to.
如果您考虑使这些多态的唯一原因是在基于 SnakeYAML 的配置中共享属性名称前缀,那么您实际上不需要引入多态关系,并且可以绑定到共享属性通过不同类中的通用字段名称.
If the only reason you are looking at making these polymorphic is to share property name prefixes in SnakeYAML based config, then you actually do not need to introduce the polymorphic relationship, and can bind to shared properties by a common field name in different classes.
有很多方法可以实现你所要求的,但是以多态的方式,这里有一些最直接的简单方法:
There are many ways to implement what you are asking for however in a polymorphic way, here are a few of the most straight forward simple ones:
不要将 @ConfigurationProperties
和 @Component
注释应用于基类,而是将它们应用于具有相同属性名称前缀的具体类.这不是我的首选方法,因为每个 bean 都不会以设置它们的属性为条件,但它可能适合您的需要.根据您的 Spring 配置是否允许重新加载属性,Spring 将维护所有 bean 上的绑定.
Instead of applying the @ConfigurationProperties
and @Component
annotations on the base class, apply them on the concrete classes, with the same property name prefix. This wouldn't be my preferred approach, as each bean would not be conditional on their properties being set, however it may suit your needs. Depending on if your Spring Configuration allows properties to be reloaded, Spring will maintain the bindings on all of the beans.
注意:从 IntelliJ Idea 2018.3 开始,添加了检查配置文件以将重复的前缀键识别为错误.您可能想忽略这一点,或禁止显示警告.
我成功测试了以下内容:
I tested the following successfully:
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
application.yml
playground:
shared-property: "shared prop"
foo-property: "foo prop"
bar-property: "bar prop"
SampleAppTest.java
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 bean
如果缺少特定属性,您可能不希望实例化某些具体实现.此外,您可能不想将 @ConfigurationProperties
和 @Component
注释耦合到每个具体类.此实现通过 Spring @Configuration
bean 构造 ConfigurationProperties bean.配置 bean 确保它们仅通过属性存在检查有条件地构造.如果没有其他 Base
bean 满足条件并且共享属性存在,则此实现还会创建具体类型 Base
的 bean.此处使用与上一个示例相同的单元测试并通过:
Polymorphic ConfigurationProperties beans conditional on properties
You may not want certain concrete implementations to be instantiated if their specific properties are missing. Furthermore, you may not want to couple the @ConfigurationProperties
and @Component
annotations to each concrete class. This implementation constructs the ConfigurationProperties beans via a Spring @Configuration
bean. The configuration bean ensures they are only constructed conditionally via a property existence check. This implementation also creates a bean of concrete type Base
if none of the other Base
beans meet conditions and the shared properties exist. The same unit test from the previous example is used here and passes:
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
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
application.yml
playground:
shared-property: "shared prop"
foo-property: "foo prop"
bar-property: "bar prop"
SampleAppTest.java
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());
}
}
这篇关于Spring Boot 中的多态配置属性的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持跟版网!