A common recurring pattern in software development is the need to select at runtime a specific instance of an interface. This instance can either be a distinct implementation class of an interface or the same class but instantiated with different properties. Spring provides unparalleled abilities to define different bean instances. These can be categorized as following:
- Each bean is a different implementation class of the interface.
- Each bean is the same implementation class but has different configuration.
- A mixture of the two above.
The canonical example is selecting a mock implementation for testing instead of the actual target production implementation. However there are often business use cases where alternate providers need to be selectively activated.
The goal is to externalize the selection mechanism by providing a way to toggle the desired bean name. We want to avoid manually commenting/uncommenting bean names inside a Spring XML configuration file. In other words, the key question is: how to toggle the particular implementation?
A brief disclaimer note: this pattern is most applicable to Spring 3.0.x and lower. Spring 3.1 introduces some exciting new features such as bean definition profiles dependent upon different environments. See the following articles for in-depth discussions:
There are two variants of this pattern:
- Single Implementation – We only need one active implementation at runtime.
- Multiple Implementations – We need several implementations at runtime so the application can dynamically select the desired one.
public interface NoSqlDao<T extends NoSqlEntity> { public void put(T o) throws Exception; public T get(String id) throws Exception; public void delete(String id) throws Exception; } public interface UserProfileDao extends NoSqlDao<UserProfile> { }
Assume two implementations of the interface:
public class CassandraUserProfileDao<T extends UserProfile> implements UserProfileDao public class MongodbUserProfileDao<T extends UserProfile> implements UserProfileDao
Single Loaded Implementation
In this variant of the pattern, you only need one implementation at runtime. Let’s assume that the name of the bean we wish to load is userProfileDao.
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); UserProfileDao userProfileDao = context.getBean("userProfileDao",UserProfileDao.class);
The top-level applicationContext.xml file contains common global beans and an import statement for the desired provider. The value of the imported file is externalized as a property called providerConfigFile. Since each provider file is mutually exclusive, the bean name is the same in each file.
<beans> <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="location" value="classpath:context-PropertyOverrideConfigurer.properties" /> <property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE" /> </bean> <import resource="${providerConfigFile}"/> </beans>
The provider-specific configuration files are:
applicationContextContext-cassandra.xml applicationContextContext-mongodb.xml applicationContextContext-redis.xml applicationContextContext-riak.xml applicationContextContext-membase.xml applicationContextContext-oracle.xml
For example (note the same bean name userProfileDao):
applicationContext-cassandra.xml <bean id="userProfileDao" class="com.amm.nosql.dao.cassandra.CassandraUserProfileDao" > <constructor-arg ref="keyspace.userProfile"/> <constructor-arg value="${cassandra.columnFamily.userProfile}"/> <constructor-arg ref="userProfileObjectMapper" /> </bean> applicationContext-mongodb.xml <bean id="userProfileDao" class="com.amm.nosql.dao.mongodb.MongodbUserProfileDao"> <constructor-arg ref="userProfile.collectionFactory" /> <constructor-arg ref="mongoObjectMapper" /> </bean>
At runtime you need to specifiy the value for the property providerConfigFile. Unfortunately with Spring 3.0. this has to be a system property and cannot be specified inside a properties file! This means it will work for a stand-alone Java application but not for a WAR unless you pass the value externally to the web server as a system property. This problem has been allegedly fixed in Spring 3.1 (I didn’t notice it working for 3.1.0.RC1). For example:
java -DproviderConfigFile=applicationContextContext-cassandra.xml com.amm.nosql.cli.UserProfileCli
Multiple Loaded Implementations
With this variant of the pattern, you will need to have all implementations loaded into your application context so you can later decide which one to choose. Instead of one import statement, applicationContext.xml is imports all implementations.
<import resource="applicationContextContext-cassandra.xml /> <import resource="applicationContextContext-mongodb.xml /> <import resource="applicationContextContext-redis.xml /> <import resource="applicationContextContext-riak.xml /> <import resource="applicationContextContext-membase.xml /> <import resource="applicationContextContext-oracle.xml />
Since you have one namespace, each implementation has to have a unique bean name for the UserProfileDao implementation. Using our previous example:
applicationContext-cassandra.xml <bean id="cassandra.userProfileDao" class="com.amm.nosql.dao.cassandra.CassandraUserProfileDao" > <constructor-arg ref="keyspace.userProfile"/> <constructor-arg value="${cassandra.columnFamily.userProfile}"/> <constructor-arg ref="userProfileObjectMapper" /> </bean> applicationContext-mongodb.xml <bean id="mongodb.userProfileDao" class="com.amm.nosql.dao.mongodb.MongodbUserProfileDao"> <constructor-arg ref="userProfile.collectionFactory" /> <constructor-arg ref="mongoObjectMapper" /> </bean>
Then inside your Java code you need to have a mechanism to select your desired bean, e.g. load either cassandra.userProfileDao or mongodb.userProfileDao. For example, you could have a test UI containing a dropdown list of all implementations. Or you might have a case where you even had a need to access two different NoSQL stores via a UserProfileDao interface.