nash2000
nash2000

Reputation: 341

Import Spring config file based on property in .properties file

In my Spring xml configuration I'm trying to get something like this to work:

<beans>

   <import resource="${file.to.import}" />

   <!-- Other bean definitions -->

</beans>

I want to decide which file to import based on a property in a properties file. I know that I can use a System property, but I can't add a property to the JVM at startup.

Note: The PropertyPlaceHolderConfigurer will not work. Imports are resolved before any BeanFactoryPostProcessors are run. The import element can only resolve System.properties.

Does anyone have a simple solution to this? I don't want to start subclassing framework classes and so on...

Thanks

Upvotes: 34

Views: 51220

Answers (10)

gabbon
gabbon

Reputation: 1

If I add the JVM argument below and have the file myApplicationContext.dev.xml, spring does load

-DmyEnvironment=dev

<context:property-placeholder />

<import resource="classpath:/resources/spring/myApplicationContext.${myEnvironment}.xml"/>

Upvotes: 0

Antoine Meyer
Antoine Meyer

Reputation: 362

Another workaround which does not rely on system properties is to load the properties of all the files using a different PropertyPlaceholderConfigurer for each file and define a different placeholderPrefix for each of them. That placeholderprefix being configured by the initial property file.



Define the first property file: (containing either first or second)

global.properties

fileToUse=first


Define the files containing a property that can be switched depending on the property defined just above:

first.properties

aProperty=propertyContentOfFirst

second.properties

aProperty=propertyContentOfSecond


Then define the place holders for all the files:

<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="locations">
        <list>
            <value>classpath:global.properties</value>
        </list>
    </property>
</bean>
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="placeholderPrefix" value="first{" />
    <property name="locations">
        <list>
            <value>classpath:first.properties</value>
        </list>
    </property>
</bean>
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="placeholderPrefix" value="second{" />
    <property name="locations">
        <list>
            <value>classpath:second.properties</value>
        </list>
    </property>
</bean>


Use the property defined in global to identify the resource to use from the other file:

${fileToUse}{aProperty}

Upvotes: 0

Russ Bateman
Russ Bateman

Reputation: 18643

André Schuster's answer, which I bumped, helped me solve a very similar issue I was having in wanting to find a different expression of properties depending on whether I was running on my own host, by Jenkins on our build host or in "real" deployment. I did this:

<context:property-placeholder location="file:///etc/myplace/database.properties" />

followed later by

<bean id="propertyConfigurer"
      class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="locations">
        <list>
            <value>WEB-INF/classes/resources/database.properties</value>
            ...
        </list>
    </property>
</bean>

which solved my problem because on my development host, I put a link to my own copy of database.properties in /etc/myplace/database.properties, and a slightly different one on the server running Jenkins. In real deployment, no such file is found, so Spring falls back on the "real" one in resources in my class files subdirectory. If the properties in question have already been specified by the file on /etc/myplace/database.properties, then (fortunately) they aren't redefined by the local file.

Upvotes: 0

Istv&#225;n
Istv&#225;n

Reputation: 508

For the Spring 2.5 and 3.0, I have a similar solution to louis, however I've just read about 3.1's upcoming feature: property management, which sounds great too.

Upvotes: 6

Andr&#233; Schuster
Andr&#233; Schuster

Reputation: 165

I'm using Spring 3 and load a properties like that:

<context:property-placeholder location="/WEB-INF/my.properties" />

Upvotes: -2

Ian Brandt
Ian Brandt

Reputation: 121

There is an old issue on the Spring JIRA for adding properties placeholder support for import (SPR-1358) that was resolved as "Won't Fix", but there has since been a proposed solution using an EagerPropertyPlaceholderConfigurer.

I've been lobbying to have SPR-1358 reopened, but no response so far. Perhaps if others added their use cases to the issue comments that would help raise awareness.

Upvotes: 5

Jaan
Jaan

Reputation: 2340

If what you want is to specify the imported XML file name outside applicationContext.xml so that you could replace applicationContext.xml without losing the configuration of the imported XML file path, you can just add an intermediate Spring beans XML file, say, confSelector.xml, so that applicationContext.xml imports confSelector.xml and confSelector.xml only contains an <import> element that refers to the suitable custom beans XML file.

Another means that might be of use are XML entities (defined by adding <!ENTITY ... > elements into the DTD declaration at the beginning of XML). These allow importing XML fragments from other files and provide "property placeholder"-like functionality for any XML file.

Neither of these solutions allows you to have the configuration file in Java's .properties format, though.

Upvotes: 1

Louis Marascio
Louis Marascio

Reputation: 2679

This is, unfortunately, a lot harder than it should be. In my application I accomplished this by doing the following:

  1. A small, "bootstrap" context that is responsible for loading a PropertyPlaceholderConfigurer bean and another bean that is responsible for bootstrapping the application context.

  2. The 2nd bean mentioned above takes as input the "real" spring context files to load. I have my spring context files organized so that the configurable part is well known and in the same place. For example, I might have 3 config files: one.onpremise.xml, one.hosted.xml, one.multitenant.xml. The bean programmatically loads these context files into the current application context.

This works because the context files are specified as input the the bean responsible for loading them. It won't work if you just try to do an import, as you mentioned, but this has the same effect with slightly more work. The bootstrap class looks something like this:

 public class Bootstrapper implements ApplicationContextAware, InitializingBean {

    private WebApplicationContext context;
    private String[] configLocations;
    private String[] testConfigLocations;
    private boolean loadTestConfigurations;

    public void setConfigLocations(final String[] configLocations) {
        this.configLocations = configLocations;
    }

    public void setTestConfigLocations(final String[] testConfigLocations) {
        this.testConfigLocations = testConfigLocations;
    }

    public void setLoadTestConfigurations(final boolean loadTestConfigurations) {
        this.loadTestConfigurations = loadTestConfigurations;
    }

    @Override
    public void setApplicationContext(final ApplicationContext applicationContext) throws BeansException {
        context = (WebApplicationContext) applicationContext;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        String[] configsToLoad = configLocations;

        if (loadTestConfigurations) {
            configsToLoad = new String[configLocations.length + testConfigLocations.length];
            arraycopy(configLocations, 0, configsToLoad, 0, configLocations.length);
            arraycopy(testConfigLocations, 0, configsToLoad, configLocations.length, testConfigLocations.length);
        }

        context.setConfigLocations(configsToLoad);
        context.refresh();
    }
}

Basically, get the application context, set its config locations, and tell it to refresh itself. This works perfectly in my application.

Hope this helps.

Upvotes: 15

laz
laz

Reputation: 28638

Add something similar to the following:

<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="ignoreResourceNotFound"><value>true</value></property>
    <property name="locations">
        <list>
            <value>classpath:propertyfile.properties</value>
        </list>
    </property>
</bean>

Upvotes: 1

Brian Agnew
Brian Agnew

Reputation: 272217

Why not:

  1. read your properties file on startup
  2. that will determine which Spring config to load
  3. whichever Spring config is loaded sets specific stuff, then loads a common Spring config

so you're effectively inverting your current proposed solution.

Upvotes: 2

Related Questions