3

I'm currently trying to move an existing spring-application to spring-boot and therefore recreate things that worked without boot.

I want to configure some properties (like spring.datasource.*) from an external source. specificly a folder with several properties files.

I set up a configuration class that creates propertyPlaceholder configurers like this:

@Configuration
public class PropertySourceConfiguration {

@Bean
public static PropertySourcesPlaceholderConfigurer defaultsPlaceHolderConfigurer() throws IOException {
    PropertySourcesPlaceholderConfigurer propertyConfigurer = new PropertySourcesPlaceholderConfigurer();
    propertyConfigurer.setLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:/*-defaults.properties"));
    propertyConfigurer.setIgnoreUnresolvablePlaceholders(true);
    return propertyConfigurer;
}

@Bean
public static PropertySourcesPlaceholderConfigurer externalPlaceHolderConfigurer() throws IOException {
    PropertySourcesPlaceholderConfigurer propertyConfigurer = new PropertySourcesPlaceholderConfigurer();
    propertyConfigurer.setLocations(new
            PathMatchingResourcePatternResolver().getResources("file:/my-config-path/*.properties"));
    propertyConfigurer.setOrder(1);
    propertyConfigurer.setIgnoreUnresolvablePlaceholders(true);
    return propertyConfigurer;
}

This seems to work for most things (like amqp or my own config properties) but when i try to use spring-data-jpa they are ignored. basicly setting spring.datasource.url (and other things used for auto-config) in those files has no effect.

looking through the logs of the PropertySourcesPropertyResolver i figured out that these configurer fall under the localProperties group which is not used when looking for spring.datasource.*.

is there a way to fix this or a better way to add external properties files to my context?

I know i could set spring.config.location to do something similar but i can not pass command-line properties to my application and need to do this config from within my application. afaik this is not possible with this property.

EDIT: setting spring.config.location:

Attempt 1:

public class ServletInitializer extends SpringBootServletInitializer {

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        return application.sources(CampaignServiceStarter.class);
    }
    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        super.onStartup(servletContext);
        servletContext.setInitParameter("spring.config.location", "file:/my-config-path/*.properties");
    }
}

Attempt 2:

public class ServletInitializer extends SpringBootServletInitializer {

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        return application.sources(CampaignServiceStarter.class).properties("spring.config.location=file:/my-config-path/*.properties");
    }
}

in both cases the external properties were not picked up at all (even in places where it worked before, like the amqp config)

Laures
  • 5,389
  • 11
  • 50
  • 76
  • Just add `@PropertySource` to your configuration, don't add additional `PropertySourcesPlaceholderConfigurer`s. Or add an `ApplicationContextInitializer` which adds additional property sources. But your current solution isn't going to work. Also why not just put everything in `application.properties` and let spring boot take care of everything? – M. Deinum May 11 '15 at 15:29
  • why not all `applicaton.properties` file: because different environments (as in us/eu/staging, ...) require different configurations. is there no way to do this with placeholders? `@PropertySources` does not support this. – Laures May 11 '15 at 15:43
  • then add `application-us.properties`. Spring has profiles and spring boot leverages that to load profile specific property files. Also your application.properties can easily differ from environment to environment. I strongly suggest a read of[ this section](http://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-external-config) of the Spring Boot Reference guide. [Section 23.3](http://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-external-config-application-property-files) explains the loading order. – M. Deinum May 11 '15 at 15:46
  • lets just stick with: it has to be external, not in the working directory of my server and not maintained by the developer. i'm considering giving my external config files fixed names so i can use `@PropertySource`. I've read the documentation already but i was hoping that there is a way to use wildcards for config paths as this was never an issue in the past. – Laures May 11 '15 at 15:53
  • 1
    Why would it need to be maintained by the developer? If it is in the same directory or a config directory it can be maintained by anyone who has access to the server. Also why wouldn't you be able to pass properties to your application? If you have a main method you can modify the incoming arguments list and add a `spring.config.location` if you use the `SpringBootServletInitializer` you can add it as default argument. Also be aware that it doesn't have to be a command line property a JNDI or environment variable . – M. Deinum May 11 '15 at 16:20
  • a config directory would work but require setting the `spring.config.location`, which i cant get to work. i cant pass command-line parameters to jetty and setting it in the `SpringBootServletInitializer` did not work. i tried setting a default property and adding it as an init-parameter to the servlet context (also in the Initializer). neither had any effect on the `ConfigFileApplicationListener`. – Laures May 11 '15 at 16:29
  • How did you set in in the `SpringBootServletInitializer`? Can you add the code to your post. – M. Deinum May 11 '15 at 17:02
  • i added the initializer source, its only missing imports – Laures May 12 '15 at 08:30
  • 1
    It is a location not a pattern, this location is used to search for `application.properties` files (and nested `config` locations). So it should be something like `spring.config.location=file:/my-config-path` and it can take a comma separated value to include multiple paths. To change the file name use `spring.config.name`. – M. Deinum May 12 '15 at 08:33
  • so basicly boot killed wildcards entirely? – Laures May 12 '15 at 08:48
  • 1
    Spring Boot has opinionated defaults. Also wildcards (can be) quite troublesome because you don't have any control over the ordering on how those files are loaded. Could also be dangerous as if some one accidentally drops a props file in the wrong directory your application can go boom. If you really want you can (and should) implement an `ApplicationContextListener` and add the `PropertySource`s to the `Environment`. – M. Deinum May 12 '15 at 09:13

1 Answers1

5

How to use external configuration is explained in this section of the Spring Boot Reference Guide.

The spring.config.location is a path to the directory which contains your application.properties file. It takes a comma separated list of values so you could specify multiple paths. It doesn't take the wildcard. It is a path so not an expression to match multiple property files. If you want to change the default application then use the spring.config.name to make it something else.

The defaults of Spring Boot are opinionated as the rest of Spring Boot (with the default configuration etc.).

If you want to do more extensive (pre) configuration you should use an ApplicationContextInitializer to manually add the PropertySources to the Environment. This is mentioned here in the Spring Boot Reference Guide.

An example of how the initializer might look.

public class ConfigurationInitializer implements ApplicationContextInitializer {

    private static final String DEFAULT_PROPS = "classpath*:/*-defaults.properties";
    private static final String EXTERNAL_PROPS = "file:/my-config-path/*.properties";

    public void initialize(ConfigurableApplicationContext applicationContext) {
        final Resource[] defaultConfigs = applicationContext.getResources(DEFAULT_PROPS);
        final Resource[] externalConfigs = applicationContext.getResources(EXTERNAL_PROPS);

        final ConfigurableEnvironment env = applicationContext.getEnvironment();
        final MutablePropertySources mps =  env.getPropertySources();
        for (Resource r : externalConfigs) {
            mps.addLast(new ResourcePropertySource(r.getFilename(), r);
        }
        for (Resource r : defaultConfigs) {
            mps.addLast(new ResourcePropertySource(r.getFilename(), r);
        }   
    }
}

Then when building your application object add it as follows.

public class ServletInitializer extends SpringBootServletInitializer {

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        return application.sources(CampaignServiceStarter.class)
            .initializers(new ConfigurationInitializer());
    }
}

Now the configs should be added to the list of property sources.

M. Deinum
  • 115,695
  • 22
  • 220
  • 224
  • The issue with this solution is that the logging properties will be ignored and the logging system like logback will not work. I was working on this and found a solution that also loads the logging properties, basically `spring.config.location` has to be set at the beginning when Spring Boot is configured, you can check it [here](http://stackoverflow.com/questions/31017064/how-to-externalize-spring-boot-application-properties-to-tomcat-lib-folder) – Daniel Mora Jun 26 '15 at 15:26