November 26

Simple environmental independence with Spring

One of the basic problems most projects run into sooner or later is handling multiple environments. Database connections, web service hosts, and other parameters will vary between development, testing, and production environments. Beyond a certain point, editing each of the configuration files becomes too time-consuming and error-prone, and an automatic approach to solve the problem is needed.

A number of approaches have already been well documented, including using multiple Ant targets and using differing Maven profiles for each environment. These all have one basic problem, however – they result in one output (WAR/EAR/JAR file) per environment. Anyone who’s accidentally shipped a test WAR to the production environment (and watched everything break as a result) can attest to how nice it would be to have a single output that can be used in any environment.

For those using the Spring Framework, a simple solution exists to this problem. A fairly commonly used part of the framework is the PropertyPlaceholderConfigurator, which will allow property names to be specified in lieu of values in the Spring configuration files. These property names are replaced with the contents of specified property files at runtime. For example:

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
  <bean id="propertyConfigurer"
        class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="locations">
      <list>
        <value>classpath:jdbc.properties</value>
      </list>
    </property>
  </bean></beans>
  <bean id="dataSource"
        class="org.apache.commons.dbcp.BasicDataSource"
        destroy-method="close">
    <property name="driverClassName" value="${jdbc.connection.driver_class}"/>
    <property name="url" value="${jdbc.connection.url}"/>
    <property name="username" value="${jdbc.connection.username}"/>
    <property name="password" value="${jdbc.connection.password}"/>
    <property name="defaultAutoCommit" value="false"/>
  </bean>

Nothing really new here. As specified above, we still need a seperate jdbc.properties file for each environment. This doesn’t really get us any closer to our goal.

However, there’s a little-documented feature of the PropertyPlaceholderConfigurer: system properties can be used when specifying the property file names. When this is the case, the system properties will be evaluated prior to the property files being loaded. This then allows us to create a single definition for all environments:

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
  <bean id="propertyConfigurer"
        class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="locations">
      <list>
        <value>classpath:jdbc-${env}.properties</value>
      </list>
    </property>
  </bean></beans>
  <bean id="dataSource"
        class="org.apache.commons.dbcp.BasicDataSource"
        destroy-method="close">
    <property name="driverClassName" value="${jdbc.connection.driver_class}"/>
    <property name="url" value="${jdbc.connection.url}"/>
    <property name="username" value="${jdbc.connection.username}"/>
    <property name="password" value="${jdbc.connection.password}"/>
    <property name="defaultAutoCommit" value="false"/>
  </bean>

And voila, we have what we’re looking for. The output WAR can ship with configuration files for each environment – jdbc-dev.properties, jdbc-test.properties, and so forth. As long as the env system property is set prior to starting the application, the same output file can be used in any environment.

Now this isn’t a perfect solution. Configuration files outside the Spring context (such as those used for log4j or ehcache) can not be managed directly by this approach – in order to have separate log settings for each environment, you’ll need to either use one of the above approaches to build multiple outputs, or manage the configuration in the Spring context. More about that some other time…

Comments

  1. David Palmer said on November 26th, 2007

    Yeah I’ve become a fan of managing application-specific configuration/settings in Spring. I just create a “spring bean” that represents all of my configuration and specify said settings in the application context. Usually we’ll break those out into a “sub” application context, but it does mean we don’t have to worry about yet another properties file (man, jee apps have a lot of properties file…. sheesh)

  2. Molecular Voices » Sensitive Properties Files Give You The Willies? said on April 14th, 2008

    [...] a good follow-up to Don’s piece, comes a new (Java) library that can make many of your security nightmares go away (with respect to [...]

  3. C. Irwin said on July 14th, 2008

    I tried this solution, and got a java io file not found exception. Does this solution work with Spring 2.5?

  4. Don Brinker said on July 14th, 2008

    We’ve used it successfully in a number of Spring 2.5 projects. The one thing that often trips people up is you need to have the system property set in your application server/servlet runner. For some platforms (Tomcat, for example) this is simple, while others (such as WebSphere) are far less straightforward.

    Look at your actual error. If the file not found contains your substitution variable name (${env}, in this example) that’s probably what’s wrong.

  5. C. Irwin said on July 14th, 2008

    So, there must be a system variable having the name ‘env’ on each applicable server, with the requisite value, dev, qa, or prod? We have a system environment variable called SYSTEM_TYPE with these values? Can ‘SYSTEM_TYPE” be used?

  6. Don Brinker said on July 14th, 2008

    As long as it’s a Java system property, sure. You’d just use classpath:jdbc-${SYSTEM_TYPE}.properties instead, to follow with the previous example.

  7. C. Irwin said on July 14th, 2008

    I took a closer look at the xml for the propertyplaceholderconfigerer bean, and noted that there were two properties, location and locations. I had used location which takes a simple string rather than locations which takes an arraylist. The dynamic database selection now works. Thank you

    classpath:jdbc-${SYSTEM_TYPE}.properties

Add a comment
Technorati Profile

Browse posts by month

Browse by author

We're always looking for rockstars

Come take a look at careers with Molecular