如何构建基于数据库的Spring Boot环境/属性源?

时间:2016-10-04 08:37:23

标签: spring spring-boot

目标是使用包含密钥和环境的环境运行Spring Boot应用程序。由数据库连接(DataSource)加载和生成的值。

或者,更抽象的定义:虽然文件配置应该是首选(更快,更容易,更宽容......),但有时您会发现需要基于非静态文件的配置的用例。

Spring 3.1引入了Environment,它实际上是属性解析器(扩展PropertyResolver)并基于对象列表PropertySource。这样的源是属性(文件或对象),地图或其他东西的包装器/适配器。看起来这就是如何获得的方式。

Properties properties = new Properties();
properties.put("mykey", "in-config");
PropertiesPropertySource propertySource = new PropertiesPropertySource("myProperties", properties);

但是,这不能在@Configuration类中完成,因为它必须可用于 配置阶段。想想像

这样的东西
@Bean public MyService myService() {
  if ("one".equals(env.getProperty("key")) {
    return new OneService();
  } else {
    return new AnotherService();
  }
}

// alternatively via
@Value("${key}")
private String serviceKey;

此外,最新的Spring版本也支持Condition

使用OneCondition之类的

public class OneCondition implements Condition {
  @Override
  public boolean matches(final ConditionContext context, final AnnotatedTypeMetadata metadata) {
    return "one".equals(context.getEnvironment().getProperty("key"));
  }
}

这可以像

一样使用
@Bean
@Conditional(OneCondition.class)
public MyService myService() {
    return new OneService();
}

我的非工作想法:

选项1:@PropertySource

相应的注释处理器仅处理文件。这很好,但不适用于这个用例。

选项2:PropertySourcesPlaceholderConfigurer

自定义属性源的示例是

@Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() throws IOException {
  PropertySourcesPlaceholderConfigurer pspc = new PropertySourcesPlaceholderConfigurer();
  pspc.setIgnoreUnresolvablePlaceholders(Boolean.TRUE);

  // create a custom property source and apply into pspc
  MutablePropertySources propertySources = new MutablePropertySources();
  Properties properties = new Properties();
  properties.put("key", "myvalue");
  final PropertiesPropertySource propertySource = new PropertiesPropertySource("pspc", properties);
  propertySources.addFirst(propertySource);
  pspc.setPropertySources(propertySources);

  pspc.setLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:application.properties"));
    return pspc;
}

但是,只有配置占位符(即@Value。任何environment.getProperty()都不会获利。

这或多或少与选项1相同(更少魔法,更多选项)。

你知道更好的选择吗?理想情况下,解决方案将使用上下文数据源。但是,这在概念上是个问题,因为数据源bean的创建依赖于属性本身......

1 个答案:

答案 0 :(得分:3)

Spring Boot为这个早期处理步骤提供了一些不同的扩展点:http://docs.spring.io/spring-boot/docs/current/reference/html/howto-spring-boot-application.html#howto-customize-the-environment-or-application-context

在内部,这些选项通过标准Spring ApplicationContextInitializer的实现来实现。

根据来源的优先级,键/值将在environment.getProperty()以及属性占位符中可用。

因为这些是预配置上下文侦听器,所以没有其他bean可用,例如DataSource。因此,如果应该从数据库中读取属性,则必须手动构建数据源和连接(最终是分离的数据源连接查找)。

选项:ApplicationEnvironmentPreparedEvent的ApplicationListener

构建使用ApplicationEnvironmentPreparedEvent

的应用程序侦听器的实现

META-INF/spring.factories和密钥org.springframework.context.ApplicationListener

中注册

- 或 -

使用SpringApplicationBuilder

new SpringApplicationBuilder(App.class)
        .listeners(new MyListener())
        .run(args);

实施例

public class MyListener implements ApplicationListener<ApplicationEnvironmentPreparedEvent> {
  @Override
  public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
    final ConfigurableEnvironment env = event.getEnvironment();
    final Properties props = loadPropertiesFromDatabaseOrSo();
    final PropertiesPropertySource source = new PropertiesPropertySource("myProps", props);
    environment.getPropertySources().addFirst(source);
  }
}

参考:http://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-spring-application.html#boot-features-application-events-and-listeners

选项:SpringApplicationRunListener

除了特殊事件之外,还有一个更通用的事件监听器,其中包含多种类型事件的挂钩。

构建SpringApplicationRunListener的实施,并在META-INF/spring.factories和密钥org.springframework.boot.SpringApplicationRunListener中注册。

实施例

public class MyAppRunListener implements SpringApplicationRunListener {

  // this constructor is required!
  public MyAppRunListener(SpringApplication application, String... args) {}

  @Override
  public void environmentPrepared(final ConfigurableEnvironment environment) {

    MutablePropertySources propertySources = environment.getPropertySources();

    Properties props = loadPropertiesFromDatabaseOrSo();
    PropertiesPropertySource propertySource = new PropertiesPropertySource("myProps", props);
    propertySources.addFirst(propertySource);
  }

  // and some empty method stubs of the interface…

}

参考:http://docs.spring.io/spring-boot/docs/current/reference/html/howto-spring-boot-application.html#howto-customize-the-environment-or-application-context

选项:ApplicationContextInitializer

这是所有人的老朋友&#34; non Boot&#34; Spring开发者。但是,SpringApplication首先会嘲笑配置。

构建ApplicationContextInitializer

的实现

META-INF/spring.factories和密钥org.springframework.context.ApplicationContextInitializer中注册。

- 或 -

使用SpringApplicationBuilder

new SpringApplicationBuilder(App.class)
        .initializers(new MyContextInitializer())
        .run(args);

实施例

public class MyContextInitializer implements ApplicationContextInitializer {
  @Override
  public void initialize(final ConfigurableApplicationContext context) {
    ConfigurableEnvironment environment = context.getEnvironment();

    MutablePropertySources propertySources = environment.getPropertySources();

    Properties props = loadPropertiesFromDatabaseOrSo();
    PropertiesPropertySource propertySource = new PropertiesPropertySource("myProps", props);
    propertySources.addFirst(propertySource);
  }

}