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 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);
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;
Additionally, the more recent Spring releases support Condition
as well.
With a OneCondition
like
public class OneCondition implements Condition {
@Override
public boolean matches(final ConditionContext context, final AnnotatedTypeMetadata metadata) {
return "one".equals(context.getEnvironment().getProperty("key"));
}
}
This can be used like
@Bean
@Conditional(OneCondition.class)
public MyService myService() {
return new OneService();
}
My non working ideas:
Option 1: @PropertySource
The corresponding annotation processor handles files only. This is fine, but not for this use case.
Option 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;
}
However, this only configures the placeholders (i.e. @Value
. Any environment.getProperty()
will not profit.
This is more or less the same as Option 1 (less magic, more options).
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...
Environment-Specific Properties File. If we need to target different environments, there's a built-in mechanism for that in Boot. We can simply define an application-environment. properties file in the src/main/resources directory, and then set a Spring profile with the same environment name.
So in a spring boot application, application. properties file is used to write the application-related property into that file.
Spring @PropertySource annotation is used to provide properties file to Spring Environment. This annotation is used with @Configuration classes. Spring PropertySource annotation is repeatable, means you can have multiple PropertySource on a Configuration class.
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
Internally, these options are realised with implementations of standard Spring ApplicationContextInitializer
.
Depending on the priority of the source, the key/value will be available both in environment.getProperty()
as well in property placeholders.
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).
Option: ApplicationListener for ApplicationEnvironmentPreparedEvent
Build an implementation of an application listener consuming ApplicationEnvironmentPreparedEvent
s and
register it in META-INF/spring.factories
and the key org.springframework.context.ApplicationListener
- or -
use the SpringApplicationBuilder
:
new SpringApplicationBuilder(App.class)
.listeners(new MyListener())
.run(args);
Example
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);
}
}
Reference: http://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-spring-application.html#boot-features-application-events-and-listeners
Option: SpringApplicationRunListener
Besides the special event, there is also a more general event listener containing hooks for several types of events.
Build an implementation of SpringApplicationRunListener
and register it in META-INF/spring.factories
and the key org.springframework.boot.SpringApplicationRunListener
.
Example
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…
}
Reference: http://docs.spring.io/spring-boot/docs/current/reference/html/howto-spring-boot-application.html#howto-customize-the-environment-or-application-context
Option: ApplicationContextInitializer
This is an old friend for all "non Boot" Spring developers. However, SpringApplication
mocks a configuration away -- at first.
Build an implementation of ApplicationContextInitializer
and
register it in META-INF/spring.factories
and the key org.springframework.context.ApplicationContextInitializer
.
- or -
use the SpringApplicationBuilder
:
new SpringApplicationBuilder(App.class)
.initializers(new MyContextInitializer())
.run(args);
Example
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);
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With