Spring Boot中的多态配置属性 [英] Polymorphic configuration properties in Spring Boot

查看:896
本文介绍了Spring Boot中的多态配置属性的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想通过Spring的@ConfigurationProperties注释在Spring上使用多态配置属性.

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?

推荐答案

尽管可以实现自定义PropertySource和/或

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

Spring在将相同属性绑定到多个bean上没有问题.您的实现无法正常工作的原因是,您只在基类上使用@Component批注向ApplicationContext注册了一个bean.这告诉组件扫描程序只有一个Base类型的单例.因为FooBar没有注册为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 Configuration是否允许重新加载属性,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

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

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中的多态配置属性的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆