Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I build a database-based Spring Boot environment / property source?

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...

like image 755
knalli Avatar asked Oct 04 '16 08:10

knalli


People also ask

How do I set environment properties in spring boot?

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.

Where do you put database properties in spring boot application?

So in a spring boot application, application. properties file is used to write the application-related property into that file.

What is a property source How would you use PropertySource?

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.


1 Answers

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

}
like image 67
knalli Avatar answered Oct 21 '22 11:10

knalli