javaspring-batchspring-batch-adminsystem-propertiesproperty-placeholder

Use of SystemPropertyInitializer to set System Property before setting property placeholder


According to this answer, you can use the Spring Batch class org.springframework.batch.support.SystemPropertyInitializer to set a System Property during startup of a Spring Context.

In particular, I was hoping to be able to use it to set ENVIRONMENT because part of Spring Batch config reads:

<bean id="placeholderProperties" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="locations">
        <list>
            <value>classpath:/org/springframework/batch/admin/bootstrap/batch.properties</value>
            <value>classpath:batch-default.properties</value>
            <value>classpath:batch-${ENVIRONMENT:hsql}.properties</value>
        </list>
    </property>
    <property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE" />
    <property name="ignoreResourceNotFound" value="true" />
    <property name="ignoreUnresolvablePlaceholders" value="false" />
    <property name="order" value="1" />
</bean>

But SystemPropertyInitializer uses afterPropertiesSet() to set the System Property, and apparently this happens after the configuration of PropertyPlaceholderConfigurer.

Is it possible to achieve this?


Solution

  • The easiest solution would be to pass in the environment property as a command-line argument, so it can be resolved as a system property.

    If that's not an option you can implement a ApplicationContextInitializer that promotes environment properties to system properties.

    public class EnvironmentPropertyInitializer implements 
                       ApplicationContextInitializer<ConfigurableApplicationContext> {
    
        boolean override = false; //change if you prefer envionment over command line args
    
        @Override
        public void initialize(final ConfigurableApplicationContext applicationContext) {
            for (Entry<String, String> environmentProp : System.getenv().entrySet()) {
                String key = environmentProp.getKey();
                if (override || System.getProperty(key) == null) {
                    System.setProperty(key, environmentProp.getValue());
                }
            }
        }
    }
    

    Here it looks like you're using Spring Batch Admin, so you can register your initializer with a slight addition to the web.xml file:

    <context-param>
        <param-name>contextInitializerClasses</param-name>
        <param-value>org.your.package.EnvironmentPropertyInitializer</param-value>
    </context-param>
    

    Adding Background Since a Comment Didn't Seem Sufficient: Here's the relevant classes and the order in which they are called/evaluated.

    1. The ApplicationContextInitializer tells the Spring Application how to load an application context and can be used to set bean profiles, and change other aspects of the context. This gets executed before the context gets completely created.
    2. The PropertyPlaceholderConfigurer is a BeanFactoryPostProcessor and calls postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory). This modifies the BeanFactory to allow for resolution of properties like ${my.property:some.default} when setting the properties of a bean as it is created by the BeanFactory.
    3. The SystemPropertyInitializer implements InitializingBean and calls afterPropertiesSet(). This method runs after a bean is instantiated and the properties have been set.

    So you're right that the SystemPropertyInitializer will not help here since it evaluates after the properties are set on the PropertyPlaceholderConfigurer. The ApplicationContextInitializer, however, will be able to promote those environment properties to system properties so they can be interpreted by the XML.

    And one more note that I forgot to mention, one of the first declared beans will need to be:

     <context:property-placeholder/>
    

    Though it seems redundant, it will allow your PropertyPlaceholderConfigurer bean to evaluate ${ENVIRONMENT:hsql} correctly by using the environment properties you just promoted.