如何构建基于数据库的 Spring Boot 环境/属性源? [英] How can I build a database-based Spring Boot environment / property source?
问题描述
目标是在一个包含键 & 的环境中运行 Spring Boot 应用程序.由数据库连接 (DataSource) 加载和生成的值.
The goal is running Spring Boot application with an Environment containing keys & values loaded and generated by a database connection (DataSource).
或者,更抽象的定义:虽然只应首选文件配置(更快、更容易、更宽容,...),但有时您会发现需要基于非静态文件的配置的用例.
Or, more abstract defined: While a configuration by files only should be preferred (faster, easier, more tolerant, ...), sometimes you will find use cases where a non-static files based configuration is required.
Spring 3.1 引入了Environment
,它实际上是一个属性解析器(扩展了PropertyResolver
)并且基于对象列表PropertySource
.这样的源是属性(文件或对象)、地图或其他东西的包装器/适配器.看起来真的是这样的获取方式.
Spring 3.1 introduces Environment
which is actually a property resolver (extends PropertyResolver
) and is based on a list of objects PropertySource
. Such a source is a wrapper/adapter for a properties (file or object), a map or something else. It really looks like this is the way how to get.
Properties properties = new Properties();
properties.put("mykey", "in-config");
PropertiesPropertySource propertySource = new PropertiesPropertySource("myProperties", properties);
然而,这不能在@Configuration 类中完成,因为它必须在配置阶段可用.想想类似的事情
However, this cannot be done in @Configuration classes since it must be available for the configuration phase. Think about something like
@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
.
Additionally, the more recent Spring releases support Condition
as well.
使用 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();
}
<小时>
我的非工作想法:
My non working ideas:
选项 1:@PropertySource
相应的注解处理器只处理文件.这很好,但不适用于此用例.
The corresponding annotation processor handles files only. This is fine, but not for this use case.
选项 2:PropertySourcesPlaceholderConfigurer
具有自定义属性源的示例是
An example with a custom property source would be
@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()
都不会获利.
However, this only configures the placeholders (i.e. @Value
. Any environment.getProperty()
will not profit.
这或多或少与选项 1 相同(更少的魔法,更多的选项).
This is more or less the same as Option 1 (less magic, more options).
你知道更好的选择吗?理想情况下,该解决方案将使用上下文数据源.然而,这在概念上是一个问题,因为数据源 bean 创建依赖于属性本身......
Do you know a better option? Ideally, the solution would use the context datasource. However, this is conceptually an issue since the datasource bean creation relies on properties itself...
推荐答案
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 Boot provides some different extensions point for this early processing step: http://docs.spring.io/spring-boot/docs/current/reference/html/howto-spring-boot-application.html#howto-customize-the-environment-or-application-context
在内部,这些选项是通过标准 Spring ApplicationContextInitializer
的实现实现的.
Internally, these options are realised with implementations of standard Spring ApplicationContextInitializer
.
根据源的优先级,键/值将在 environment.getProperty()
以及属性占位符中可用.
Depending on the priority of the source, the key/value will be available both in environment.getProperty()
as well in property placeholders.
因为这些是预配置上下文侦听器,所以没有其他 bean 可用,例如 DataSource
.因此,如果要从数据库中读取属性,则必须手动构建数据源和连接(最终是单独的数据源连接查找).
Because these is a pre-config-context listeners, no other beans are available, like a DataSource
. So if the properties should be read from a database, the datasource and connection have to be build manually (eventually a separated datasource connection lookup).
选项:ApplicationEnvironmentPreparedEvent 的 ApplicationListener
构建使用 ApplicationEnvironmentPreparedEvent
s 和
在 META-INF/spring.factories
和 key 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);
}
}
选项:SpringApplicationRunListener
除了特殊事件之外,还有一个更通用的事件侦听器,其中包含多种类型事件的钩子.
Besides the special event, there is also a more general event listener containing hooks for several types of events.
构建SpringApplicationRunListener
的实现,并在META-INF/spring.factories
和键org.springframework.boot.SpringApplicationRunListener
中注册它.
Build an implementation of SpringApplicationRunListener
and register it in META-INF/spring.factories
and the 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…
}
选项:ApplicationContextInitializer
这是所有非 Boot"Spring 开发人员的老朋友.然而,SpringApplication
模拟了一个配置——起初.
This is an old friend for all "non Boot" Spring developers. However, SpringApplication
mocks a configuration away -- at first.
构建 ApplicationContextInitializer
和
在 META-INF/spring.factories
和 key org.springframework.context.ApplicationContextInitializer
中注册.
register it in META-INF/spring.factories
and the key 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);
}
}
这篇关于如何构建基于数据库的 Spring Boot 环境/属性源?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!