A common pattern for accomplishing this has been a 'search and replace' during the build process (hopefully automated using Ant or an equivalent!). There are some drawbacks to this approach:
- The artifacts you are testing in one environment are not *identical* to the (supposedly same) artifacts in another environment. Since the artifacts have been rebuilt / repackaged, this opens up a 'risk-window' during which something could go wrong. I've personally had this occur when a developer unknowingly built the production release using Java 5 (the application was 1.4 at the time), resulting in a botched production deployment even though testing was successful across all other environments.
- A 'search and replace', usually being file & text-based, can be fragile and prone to errors. Perhaps a new configuration file is added, without being added to the search pattern; perhaps there is some valid text that coincidentally matches a replacement pattern.
- On a large project with multiple environments, multiple builds can be time and space consuming, essentially multiplying your time/space needs by the number of environments.
- Replacement values are now 'hard-coded' into the deployed artifacts, and cannot be adjusted without rebuilding and redeploying (and the inherent risk therein).
Here is a sample bean definition (borrowed from PropertyPlaceholderConfigurer's JavaDoc):
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">...and the addition of a RuntimeEnvironmentPropertyPlaceholderConfigurer bean:
<property name="driverClassName" value="${jdbc.driver}" />
<property name="url" value="${jdbc.url}" />
</bean>
<util:set id="runtimeEnvironments">
<value>production</value>
<value>staging</value>
<value>integration</value>
<value>development</value>
</util:set>
<bean class="digitalascent.framework.support.environment.RuntimeEnvironmentPropertyPlaceholderConfigurer">
<property name="propertyLocation" value="/WEB-INF/runtime-properties/" />
<property name="environments" ref="runtimeEnvironments"/>
<property name="defaultEnvironment" value="development" />
</bean>
Now the properties 'jdbc.driver' and 'jdbc.url' can be defined in the environment specific properties files: WEB-INF/runtime-environment/development.properties, integration.properties, staging.properties and production.properties.
By default, the implementation looks to the system property 'runtime.environment' for the environment key (e.g. -Druntime.environment=production); this property name can be changed (its a property of the bean), or for more advanced uses a custom RuntimeEnvironmentKeyResolver can be wired to to resolve the runtime environment.
The RuntimeEnvironmentPropertyPlaceholderConfigurer also, by default, enables overriding of any properties by system properties; this allows for 'tweaking' the configuration post-deployment, without having to rebuild and redeploy. In the above example, if the hostname of your database server needs to change, you can override the jdbc.url property by adding '-Djdbc.url=newUrlHere' to the relevant startup script.
The source code can be found attached to the Spring JIRA enhancement request.
Some points to note:
- Source is based on Java 5+; it should be trivial to back-port to Java 1.4 if necessary;
- The examples are based on Spring 2.0; there is nothing in the code that should prevent use on Spring 1.2.x;
- If your application has multiple Spring contexts (say a web context and a servlet context), you will need to define the RuntimeEnvironmentPropertyPlaceholderConfigurer bean in each context
- Peter Monks for providing the use-case / inspiration for the RuntimeEnvironmentKeyResolver;
- Members of the Spring community for feedback (forum post)
- Digital Ascent for sponsoring the development of this solution