Polymorphic configuration properties in Spring Boot

前端 未结 1 1616
爱一瞬间的悲伤
爱一瞬间的悲伤 2021-02-08 18:59

I would like to use polymorphic configuration properties on Spring, using Spring\'s @ConfigurationProperties annotation.

Suppose we have the following POJO

相关标签:
1条回答
  • 2021-02-08 19:05

    Although it is possible to implement custom PropertySources and/or ConversionService, a custom deserialization provider is not necessary.

    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.

    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:

    Self declaring Polymorphic ConfigurationProperties singleton beans

    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.

    Note: As of IntelliJ Idea 2018.3, an inspection profile was added to identify duplicate prefix keys as an error. You may want to ignore this, or suppress the warnings.

    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

    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());
        }
    }
    

    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

    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());
        }
    }
    
    0 讨论(0)
提交回复
热议问题