Полиморфные свойства конфигурации в Spring Boot - PullRequest
0 голосов
/ 28 декабря 2018

Я хотел бы использовать полиморфные свойства конфигурации в 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 Ответ

0 голосов
/ 29 декабря 2018

Хотя возможно реализовать пользовательские PropertySources и / или ConversionService , пользовательский поставщик десериализации не требуется.

В Spring нет проблем с привязкой одних и тех же свойств к нескольким bean-компонентам.Причина, по которой ваша реализация не работает, заключается в том, что вы регистрируете только один компонент с помощью ApplicationContext с аннотацией @Component в базовом классе.Это говорит компонентному сканеру, что существует только один синглтон типа Base.Поскольку Foo и Bar не зарегистрированы в качестве bean-компонентов, они не будут привязаны к.

Если единственная причина, по которой вы пытаетесь сделать эти полиморфные, - это использовать префиксы имен свойств в SnakeYAML на основе конфигурации, тогда вам фактически не нужно вводить полиморфные отношения, и вы можете привязать к общим свойствам общее имя поля в разных классах.

Есть много способов реализовать то, что вы запрашиваетеоднако, полиморфным образом, вот некоторые из самых простых простых:

Самообъявленные полиморфные синглтон-компоненты ConfigurationProperties

Вместо применения аннотаций @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, зависящие от свойств

Возможно, вы не захотите создавать экземпляры определенных конкретных реализаций, если отсутствуют их конкретные свойства.Кроме того, вы можете не связывать аннотации @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());
    }
}
...