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

kna*_*lli 5 spring spring-boot

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

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


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

Properties properties = new Properties();
properties.put("mykey", "in-config");
PropertiesPropertySource propertySource = new PropertiesPropertySource("myProperties", properties);
Run Code Online (Sandbox Code Playgroud)

但是,这不能在@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;
Run Code Online (Sandbox Code Playgroud)

此外,最近的 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"));
  }
}
Run Code Online (Sandbox Code Playgroud)

这可以像

@Bean
@Conditional(OneCondition.class)
public MyService myService() {
    return new OneService();
}
Run Code Online (Sandbox Code Playgroud)

我的非工作想法:

选项 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;
}
Run Code Online (Sandbox Code Playgroud)

但是,这仅配置占位符(即@Value. Anyenvironment.getProperty()将无利可图。

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


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

kna*_*lli 6

Spring Boot 为这个早期处理步骤提供了一些不同的扩展点:http : //docs.spring.io/spring-boot/docs/current/reference/html/howto-spring-boot-application.html#howto-customize-the -环境或应用程序上下文

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

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

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


选项:ApplicationEnvironmentPreparedEvent 的 ApplicationListener

构建使用ApplicationEnvironmentPreparedEvents 和的应用程序侦听器的实现

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

- 或者 -

使用SpringApplicationBuilder:

new SpringApplicationBuilder(App.class)
        .listeners(new MyListener())
        .run(args);
Run Code Online (Sandbox Code Playgroud)

例子

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);
  }
}
Run Code Online (Sandbox Code Playgroud)

参考: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和 key 中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…

}
Run Code Online (Sandbox Code Playgroud)

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


选项:ApplicationContextInitializer

这是所有“非 Boot”Spring 开发人员的老朋友。但是,首先SpringApplication模拟配置。

建立实施ApplicationContextInitializer和

将其注册在META-INF/spring.factories和 key 中org.springframework.context.ApplicationContextInitializer。

- 或者 -

使用SpringApplicationBuilder:

new SpringApplicationBuilder(App.class)
        .initializers(new MyContextInitializer())
        .run(args);
Run Code Online (Sandbox Code Playgroud)

例子

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

}
Run Code Online (Sandbox Code Playgroud)