Question

I would like to have a properties setup which can, on certain environments, override specific properties. For example, our default JDBC properties for dev are:

  • db.driverClassName=com.mysql.jdbc.Driver
  • db.url=jdbc:mysql://localhost:3306/ourdb
  • db.username=root
  • db.password=

The problem is that some of our devs would like to have a different username/password on the db, or possibly even a non locally hosted db. The same is true for our rabbitMQ configuration, which currently uses a similar localhost, guest/guest setup. Being able to override the properties of certain elements of this configuration on a per-developer basis would allow us to move much of the infrastructure/installation requirements for building the software off the local machine and onto dedicated servers.

I have set-up a simple project to wrap my head around the configuration required to achieve what I want, and this is my first foray into the world of spring property configuration, since up till now, property loading and management is done with some custom code. Here is my setup:

class Main_PropertyTest {
    public static void main(String[] args) {
        String environment = System.getenv("APPLICATION_ENVIRONMENT"); // Environment, for example: "dev"
        String subEnvironment = System.getenv("APPLICATION_SUB_ENVIRONMENT"); // Developer name, for example: "joe.bloggs"
        System.setProperty("spring.profiles.active", environment);
        System.setProperty("spring.profiles.sub", subEnvironment);

        try(AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(PropertyTestConfiguration.class)) {
            Main_PropertyTest main = context.getBean(Main_PropertyTest.class);
            main.printProperty();
        }
    }

    private final String property;

    public Main_PropertyTest(String property) {
        this.property = property;
    }

    public void printProperty() {
        System.out.println("And the property is: '" + property + "'.");
    }
}

And my configuration:

@Configuration
public class PropertyTestConfiguration {
    @Bean
    public static PropertySourcesPlaceholderConfigurer primaryPlaceholderConfigurer() {
        PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer();
        propertySourcesPlaceholderConfigurer.setLocation(new ClassPathResource(System.getProperty("spring.profiles.active") + ".main.properties"));
        return propertySourcesPlaceholderConfigurer;
    }

    @Bean
    public static PropertySourcesPlaceholderConfigurer secondaryPlaceholderConfigurer() {
        PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer();
        propertySourcesPlaceholderConfigurer.setLocation(new ClassPathResource(System.getProperty("spring.profiles.sub") + ".main.properties"));
        propertySourcesPlaceholderConfigurer.setIgnoreResourceNotFound(true);
        propertySourcesPlaceholderConfigurer.setIgnoreResourceNotFound(true);
        propertySourcesPlaceholderConfigurer.setOrder(-1);
        return propertySourcesPlaceholderConfigurer;
    }

    @Bean
    public Main_PropertyTest main_PropertyTest(@Value("${main.property}") String property) {
        Main_PropertyTest main_PropertyTest = new Main_PropertyTest(property);
        return main_PropertyTest;
    }
}

And for completeness, my dev.main.properties and test.main.properties:

main.property=dev

main.property=test

The main problem is that I get an illegal argument exception. As far as I can tell, what I have written should be the javaconfig equivalent of this method: http://taidevcouk.wordpress.com/2013/07/04/overriding-a-packaged-spring-application-properties-file-via-an-external-file/ Unfortunately I get the following error: java.lang.IllegalArgumentException: Could not resolve placeholder 'main.property' in string value "${main.property}". Note that I also need to take care of the case where there is no sub-environment, and this is the case I have started with (although I get the same error even if both files exist). If I remove the bean which sets up the second propertysourcesplaceholderconfigurer, then it all works fine (by which I mean dev.main.properties is loaded and "And the property is: 'dev'." is printed out).

A secondary problem is that the code doesn't look great, and each layer of the system will need two PSPC's set-up so that they can access these properties. Furthermore, it requires a lot of manual calls to System.getProperty(), since I couldn't pass ${spring.profiles.active} to PSPC.setLocation();

Note: I have tried @PropertySources({primaryproperties, secondaryProperties}), but this fails because secondaryProperties does not exist. I have also tried @Autowired Environment environment; and getting the properties from that, but the secondary PSPC causes the environment to not be autowired...

So following this lengthy explanation, my questions are:

  • Is this the right way of solving this problem?
  • If so, what is wrong with my configuration?
  • How can I simplify the configuration (if at all)?
  • Is there an alternative mechanism available which would solve my problem?

Thank you for your time! :)

Was it helpful?

Solution

Your configuration is flawed when configuring BeanFactoryPostProcessor with java config the methods should be static. However it can be even easier, instead of registering your own PropertySourcesPlaceholderConfigurer utilize the default @PropertySource support.

Rewerite your jav config to the following

@Configuration
@PropertySource(name="main", value= "${spring.profiles.active}.main.properties")
public class PropertyTestConfiguration {

    @Autowired
    private Environment env;

    @PostConstruct
    public void initialize() {
        String resource = env.getProperty("spring.profiles.sub") +".main.properties";
        Resource props = new ClassPathResource(resource);
        if (env instanceof ConfigurableEnvironment && props.exists()) {
            MutablePropertySources sources = ((ConfigurableEnvironment) env).getPropertySources();
            sources.addBefore("main", new ResourcePropertySource(props)); 
        }
    }

    @Bean
    public Main_PropertyTest main_PropertyTest(@Value("${main.property}") String property) {
        Main_PropertyTest main_PropertyTest = new Main_PropertyTest(property);
        return main_PropertyTest;
    }
}

This should first load the dev.main.properties and additionally the test.main.properties which will override the earlier loaded properties (when filled ofcourse).

OTHER TIPS

I had a similar issue with overwriting already existing properties in integration tests

I came up with this solution:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = {
    SomeProdConfig.class,
    MyWebTest.TestConfig.class
})
@WebIntegrationTest
public class MyWebTest {


    @Configuration
    public static class TestConfig {

        @Inject
        private Environment env;


        @PostConstruct
        public void overwriteProperties() throws Exception {
            final Map<String,Object> systemProperties = ((ConfigurableEnvironment) env)
                .getSystemProperties();
            systemProperties.put("some.prop", "test.value");
        }
    }
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top