問題描述
我想在 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 中的多態配置屬性的文章就介紹到這了,希望我們推薦的答案對大家有所幫助,也希望大家多多支持html5模板網!