Sunday, September 16, 2012

Spring 3.1 Bean Profiles

One of the most exciting new features (to me) introduced in Spring 3.1 is the bean definition profiles (http://blog.springsource.com/2011/02/11/spring-framework-3-1-m1-released/). Bean definition profiles are simply bean configurations that are 'activated' based on an the existence of an indicator or marker.

A lot of examples of bean definition profiles uses the JDBC DataSource bean to illustrate its usefulness. Usually for unit testing, a DriverManagerDataSource class is sufficient, as shown below:



<bean class="org.springframework.jdbc.datasource.DriverManagerDataSource" id="dataSource">
 <property name="driverClassName" value="${jdbc.driverClassName}">
 <property name="url" value="${jdbc.url}">
 <property name="username" value="${jdbc.username}">
 <property name="password" value="${jdbc.password}">
</property></property></property></property></bean>


In order to deploy the application (typically a webapp) for active use or production, the DataSource bean is usually configured to get from a JNDI lookup, as shown:


 


The JNDI lookup way also requires configuring the DataSource connection pool at the application server.

Before Spring 3.1, in order to make one of the bean definitions 'active', the desired bean definition will need to be loaded last, to override the earlier appearing bean definition e.g. the 'dataSource' bean using DriverManagerDataSource will need to be in a Spring XML file that is loaded last. One of the ways to achieve this is to specify the XML file as the last file in a comma-separated value of the context-param 'contextConfigLocation' in web.xml. An Ant task may be invoked to automate a 'deletion' of this file entry so that when deployed to production, the 'dataSource' bean using JNDI lookup will be the 'active' bean. Or, just comment out the undesired bean :). It was clumsy and led to different builds for different environments.

So, how does Spring 3.1 solve this problem? Spring 3.1 allows the incorporation of the 2 different bean definitions with the same id in the same XML file, by having nested 'beans' XML elements, as shown (simplified for clarify):



<beans xmlns="http://www.springframework.org/schema/beans" xsi:schemalocation="
     http://www.springframework.org/schema/beans 
     http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">

 <!-- Generic bean definitions ABOVE -->
  
 <beans profile="openshift,default">
  <bean class="org.springframework.jndi.JndiObjectFactoryBean" id="dataSource">
         <property name="jndiName" value="${jndi.datasource}">
     </property></bean>
 </beans>
 
 <beans profile="testing">
     <bean class="org.springframework.jdbc.datasource.DriverManagerDataSource" id="dataSource">
         <property name="driverClassName" value="${jdbc.driverClassName}">
         <property name="url" value="${jdbc.url}">
         <property name="username" value="${jdbc.username}">
         <property name="password" value="${jdbc.password}">
     </property></property></property></property></bean>
 </beans>
</beans>


A few things to note:
- The nested beans XML elements must be placed at the end of the configuration XML file
- Ensure that the Spring 3.1 Beans XSD file is used (earlier versions won't work).
- It is recommended to have one bean profile to be the default profile.

To 'activate' a bean profile, one of the ways is to pass in a system property 'spring.profiles.active'. For my testing environment, I use Tomcat, so I placed this property in setEnv.bat (not in Tomcat by default, just create it under ${TOMCAT_HOME}/bin folder, and the file will be processed by Tomcat when starting up). So for testing, the value to pass is: -Dspring.profiles.active=testing

If the 'spring.profiles.active' value is not specified, then the default profile will be the active profile.

To check or test whether which profile is active, you can get the Environment object from an ApplicationContext, as shown:

Environment env = ctx.getEnvironment();
String[] activeProfiles = env.getActiveProfiles();


In my example above, it seems that only 1 profile can be active at a time, but the Spring API allows activation of multiple profiles. Just comma-separate the profiles when specifying the value of 'spring.profiles.active'. But proceed with caution as too many profiles may lead to confusion.

1 comment:

waddle said...

No nead of any overriding by some "last" file. Just flag your two datasources as lazy, then use an alias. The bean pointed by your alias will depend on a configuration value read from a property file added to the classpath. All you have to do, is change the file of the classpath.

Spring profiles are totally useless. It's just a lazy way of doing things and it will force you to declare a "magical" (i.e.) non standard property.
Everyone seems happy to declare a env var (or add an argument to the JVM) which value is linked to an application file ! Change your profile name and you'll have to change the production script ! Awfull !