Source Allies Logo

Sharing Our Passion for Technology

& Continuous Learning

<   Back to Blog

Environment Specific Properties in Spring

On many occasions I want to be able to inject environment specific property values into my Spring managed beans. These may be things like web service endpoints, database URLs, etc. Values I know for each environment at build time, but I want to use the same WAR/EAR file in each environment. I would like to keep the actual values separate from the Spring config files themselves. And I would really like to manage a set of default values for each property, so that I do not need to specify a value for every property in every environment (ex. my credit card processing URL for dev, test, uat is the same, but for production it is different.)

Spring already has 90% of this solution already worked out. The PropertyPlaceholderConfigurer can be used to externalize property values into Java properties files. These properties can then be referenced "ant style" in your configuration files ex. ${myPropertyValues}. It can even handle multiple files, checking them in order for each property, so one file can override one or move values that are stored in another. However the PropertyPlaceholderConfigurer doesn't have any concept of an environment specific file.

Thats where we come in:

package com.sourceallies.config;

import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;

public class EnvironmentPropertyPlaceholderConfigurer extends
		PropertyPlaceholderConfigurer {

  @Override
  public void setLocation(Resource location) {
   if (!(location instanceof ClassPathResource)) {
     throw new RuntimeException(
        "EnvironmentPropertyPlaceholderConfigurer " +
        "locations must be on the classpath");
   }

   ClassPathResource cl = (ClassPathResource) location;
   String filename = cl.getFilename();

   //switch "env.properties" to "env.dev.properties"
   String envFilename = filename.replaceAll(
      "(.*)\\.properties",
      "$1." + getEnvironment().toLowerCase() + ".properties");

   Resource envLocation = cl.createRelative(envFilename);
   if (envLocation.exists()) {
     setLocations(new Resource[] { location, envLocation });
   } else {
     setLocations(new Resource[] { location });
   }
  }

  public String getEnvironment() {
   /*
    * Magic goes here. This method must be able to determine which
    * environment it is running in without any help from injected
    * properties.
    */

   return System.getProperty("sai.env", "DEV");
  }
}

This version of the configurer expects a single resource to be passed in via the location property. In this case I restrict it only to ClassPathResource instances, but really it can be any version that can generate a new resource with a relative path. The resource we configure here should be the "default" file, the configurer will then build an environment specific file based on the default name and check its existence. If the environment specific file is found the are ordered up so that any properties in the environment file will override the values in the default file. If no environment file is found, only the default file is used.

The only real magic here is how the configurer determines the environment that its running in. In this simple case, the configurer relies on a system property to tell it what environment its running in. In larger organizations I have found that getting system properties set reliably across environments to be extremely difficult (most shops still tack on application server administration to the jobs of the DBA's or the Unix/Windows admins who are already overworked.) In these cases, more extreme methods may be needed to determine the environment name. JNDI lookups, Database queries, reading files from the file system may all be good solutions. The important thing is that you can't inject the environment name, if it were that easy, we wouldn't actually need this configurer!