Полиморфные свойства конфигурации в 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());
    }
}
Другие вопросы по тегам