For every project we have a staging environment that is almost always pretty much identical to the production environment. Of course you can’t completely mimick the production environment since systems we’re integrating with are not available (in which case they’re mocked) or located in a different place. We’re doing a lot Spring-based apps and some of the features Spring offers are really useful when distinguishing between a local environment, a staging environment and the final production environment. The following discusses the possibilities you have for setting up a Spring ApplicationContext to be able to deploy it to different environments without having to change the context itself.
PropertyPlaceHolderConfigurer
Spring offers two beans that allow you to externalize some of the properties in an ApplicationContext. The first and most often used one is the PropertyPlaceholderConfigurer. This bean allows you to include ${xxxyyyzzz} tokens in your application context and have them resolved at run-time (when the application context is loaded) from an external Properties file for example. An example would be the following:
<bean id='salesForceConnection'
class='icatch.salesforce.SalesForceConnectionImpl'
singleton='false'
init-method='init' destroy-method='close'>
<property name='url'>
<value>${salesforce.url}</value>
</property>
</bean>
The connection to the Salesforce service we’re integrating with is not mentioned in the context itself. Instead we are going to provide a separate Properties file and include it there. When deploying the application on the staging environment, we do not want to connect to the live Salesforce environment, but to the development edition instead. This offers the same features so we can still test everything. So based on the environment, we’re placing a different properties file in a pre-defined location and the application itself doesn’t need to change.
# staging.properties
salesforce.url=\
https://na1-api.salesforce.com/services/Soap/c/3.0
#live.properties
salesforce.url=\
https://emea.salesforce.com/services/Soap/c/2.5
Of course we still have to tell Spring where to find the properties it needs use when replacing the tokens. This is where the so-called BeanFactoryPostProcessor comes in. A post-processor is automatically executed after a context has fully finished loading all its bean definitions. This doesn’t mean any beans have been instantiated yet! The post-processor is allowed to inspect the application context and make changes as he wishes. One of those post-processor is the PropertyPlaceholderConfigurer. Configure it using the code snippet below. As you can see, we’ve put a Properties file in a pre-defined location and give it to the Spring placeholder configurer using the location property.
<bean class='org.spr....config.PropertyPlaceholderConfigurer'>
<propert name='location'>
<value>/etc/icatch/sfconnection.properties</value>
</propert></bean>
PropertyOverrideConfigurer
The example above shows an ApplicationContext with tokens that are replaced at run-time. Another option is to have default values in your context and have a PropertyOverrideConfigurer override those. This way you don’t always have to mention a configurer in your context and provide default values for development purposes. This would look something like the following:
<bean id='salesForceConnection'
class='icatch.salesforce.SalesForceConnectionImpl'
singleton='false'
init-method='init' destroy-method='close'>
<property name='url'>
<value>${salesforce.url}</value>
</property>
<property name='username'>
<value>blabbering</value>
</property>
<property name='password'>
<value>blabbering</value>
</property>
<property name='sessionTimeoutMS'>
<value>5400000</value> <!-- 1,5 hour -->
</property>
</bean>
#live-override.properties
salesForceConnection.username=realusername
salesForceConnection.password=realpassword
Choosing beans using a configurer
(overriding bean references)
One other nifty thing you can do using placeholder and override configurers is referencing a different bean based on a properties file. This feature is not really documented anywhere but can be handy sometimes. The configurers as they exist now only override and replace property values. In other words, they’re not capable of overriding and replacing complete bean definitions. The configurers are however able to override and replace bean references. This can be quite handy when you have more complex requirements and cannot do with simply replacing a property value:
<beans>
<bean class='org.spr....config.PropertyPlaceholderConfigurer'>
<property name='properties'>
<props>
<prop key='connection.name'>connectionOne</prop>
</props>
</property>
</bean>
<bean id='service' class='ServiceObject'>
<property name='salesForceConnection'>
<ref bean='${connection.name}'/>
</property>
</bean>
<bean id='connectionOne' class='SuperConnection'>
<!-- other properties -->
</bean>
<bean id='connectionTwo' class='NotSoSuperConnection'>
<!-- other properties -->
</bean>
</beans>
Remember, usually one would split such a context in three files: one containing connectionTwo, one containing connectionOne and the other containing the service object. Based on what environment you’re in you would load a certain combinations of contexts. This is the way to go forward when selecting a data source implementation but the drawback here is that you need to override the bean itself. You cannot override the bean reference here. Using a PropertyPlaceholderConfigurer or PropertyOverrideConfigurer you can do that.
The location of your properties files
Then there is of course the issue of how to manage the different configurations. We usually have a configuration module in CVS where we keep directories for all different environments (one for staging, one for testing, one for production). Deploying a new version mean we have do the following:
- Deploy the new war file
- Overwrite the configuration directory with the directory we’ve pulled from CVS, specific to the current environment
So in case of the above examples, we’d have the following directory structure:
+-- staging
- placeholder.properties
- override.properties
+-- production
- placeholder.properties
- override.properties