Externalising Spring Configuration

Enterprise applications typically contain a number of  properties and  runtime resources that must be configured before the application is deployed. Property and resource values can either be baked into the artefact at build time using something like Maven Profiles or derived at runtime using Spring Profiles. My preferred approach is to externalise all configuration so that the artefact taken from the build server is environment agnostic and can be deployed onto any environment . For example, we could take a WAR file from our build server and  deploy it to our development, UAT or production environments without changing any configuration in the WAR file itself.

Application Property Configuration

Spring applications use the PropertyPlaceholderConfigurer to load application properties. Property files are typically loaded from the class path but the example below uses a slightly different approach, loading the properties file from a directory outside of the application itself. This allows the property file to be set-up and managed anywhere on the sever and most importantly, outside of the WAR or JAR file

<!-- ====================================================================== -->
<!--             Use this to set bean properties at run time                -->
<!-- ====================================================================== -->
<bean id="placeholderProperties" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
   <property name="locations">
      <list>        
         <!-- load environment specific config using JVM argument set on container -->
         <value>file:${applicationProperties}</value>    
      </list>
   </property>
   <property name="ignoreResourceNotFound" value="false" />
   <property name="ignoreUnresolvablePlaceholders" value="false" />
   <property name="order" value="1" />
</bean>

Lines 5 to 10 – locations attribute specifies where the properties file should be read from. In this instance we use a JVM argument to specify a properties file on the file system. We’ll look at how the JVM argument is set later.
Line 11 – ignoreResourcesNotFound attribute is set to false so that a runtime exception is thrown on application start-up if the specified resource is not found. Its better to know at start-up that the resource is missing or the directory has been incorrectly configured, than finding out at runtime.    
Line 12 – ignoreUnResolvablePlaceholders attribute is set to false so that a runtime exception is thrown on application start-up if a property referenced in the Spring configuration is not present in the property files. Again it’s better to know at start-up that a property hasn’t been configured, than finding out at runtime.
Line 13 – order attribute is set to 1 and indicates the order in which this PropertyPlaceholderConfigurer should be loaded if there are multiple instances in the application context.

Setting a JVM Argument

Next we need to set a custom JVM argument called applicationProperties – this is used in the PropertyPlaceholderConfigurer above to reference the properties file. On Tomcat we can do this by creating a file called setenv.sh in the $TOMCAT_HOME/bin directory. The values in this file will be loaded on container start up and will supplement or override JVM arguments defined in catalina.sh. We set the property file location in setenv.sh as follows.

  
export JAVA_OPTS="-DapplicationProperties=/myApp/config/myApp.properties";

Externalising Container Managed Resources

Now that we’ve externalised our application properties we’ll need to externalise container managed resources, in this example a JDBC data source. We don’t want environment specific database connection details set in the application context.xml file as we’d have to rebuild and redeploy our WAR file for each environment. Instead of having this configuration defined within the application we want to externalise the resource configuration to the container.
The context.xml file contains a resource link to our JDBC data source definition as follows.

<?xml version="1.0" encoding="UTF-8"?>
<Context antiJARLocking="true">
   <!-- References JNDI resources configured in Server.xml -->
   <WatchedResource>WEB-INF/web.xml</WatchedResource>
   <ResourceLink global="jdbc/myAppDataSource" name="jdbc/myAppDataSource" type="javax.sql.DataSource"/>
</Context>

Next we update Server.xml in var/lib/Tomcat7/conf . Inside the GlobalNamingResources element we define our container managed JDBC resource. Its important that we use the same name that was used to reference the data source from the RersourceLink in context.xml above.

<GlobalNamingResources >
   <Resource name="jdbc/myAppDataSource"
             auth= "Container"
             type= "javax.sql.DataSource"
             username= "someUsername"
             password= "somePassword"
             driverClassName="com.mysql.jdbc.Driver"
             url= "jdbc:mysql://some-url:3306/myAppDatabase"
             maxActive="8"
             maxIdle= "4"/>

</GlobalNamingResources >

That’s pretty much it. We’ve now externalised the Spring application properties and container managed resources so that our application is completely decoupled from its configuration.