Tuesday, April 22, 2008

Changing Spring Beans Using Properties

If you're reading this article, I assume you know about Spring. I'm currently using Spring 2.0.7, but the following code still holds for Spring 2.5 (although 2.5 now has the context schema if you prefer) Ill hopefully come back and update this later...

I have an application that sometimes needs to be run as a standalone app, and sometimes as a client-server. In other words, my user interface sometimes needs to talk to a command handler running in the same process, and sometimes this command handler needs to be remote.

Lets say my user interface looks like:
package com.acme;
public class UserInterface {
private CommandHandler handler;
public void setCommandHandler(CommandHandler handler) {
this.handler = handler;
}
public String do(String command) {
return handler.handleCommand(command);
}
public static void main() {
ApplicationContext ctx = ...
UserInterface ui = (UserInterface ) ctx.getBean("ui");
System.out.println(ui.do("greet"));
}
}

Where CommandHandler is a interface with a single POJO implementation of:
package com.acme;
public class CommandHandlerImpl implements CommandHandler {
public String handleCommand(String command) {
if ("greet".equals(command)) {
return "hello";
} else {
return "unrecognised command";
}
}
}
When I run this in standalone, I can wire everything like:

<bean id="commandHandler" class="com.acme.CommandHandlerImpl"/>
<bean id="ui" class="com.acme.UserInterface">
<property name="commandHandler" ref="commandHandler"/>
</bean>

Now, for the client-server version, I am hosting my CommandHandler inside tomcat, and using Spring's DispatcherServlet. The code in the web.xml deployment descriptor looks like:

<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>*.service</url-pattern>
</servlet-mapping>

For this configuration, the servlet will look in dispatcher-servlet.xml for the Spring configuration. I am using XFire (although you could use CXF or Spring WS 1.5) as a way of remoting my CommandHandler, so it looks like:

<import resource="classpath:org/codehaus/xfire/spring/xfire.xml">
<bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
<props>
<prop key="/xfire.service">remote.xfire</prop>
</props>
</property>
</bean>
<bean id="target" class="com.acme.CommandHandlerImpl" />
<bean id="remote.xfire" class="org.codehaus.xfire.spring.remoting.XFireExporter">
<property name="serviceFactory" ref="xfire.serviceFactory"/>
<property name="xfire" ref="xfire"/>
<property name="serviceBean" ref="target"/>
<property name="serviceClass" value="com.acme.CommandHandler"/>
</bean>

And when I want to connect to this my original spring XML now needs to look like:

<bean id="ui" class="com.acme.UserInterface">
<property name="commandHandler" ref="commandHandler"/>
</bean>
<bean id="commandHandler" class="org.codehaus.xfire.spring.remoting.XFireClientFactoryBean" lazy-init="true">
<!-- Assumes war is named web.war -->
<property name="wsdlDocumentUrl" value="http://localhost:8080/web/xfire.service?wsdl" />
<property name="serviceInterface" value="com.acme.CommandHandler" />
</bean>


Phew! Now the next step is to let me choose which bean is injected into the user interface. For this, I like to use a properties based approach. Spring includes a PropertyPlaceholderConfigurer which is normally used for replacing string values, so for example my wsdlDocumentUrl property could have been specified as "http://${com.acme.host}:${com.acme.port}/web/xfire.service?wsdl", and we could have used a PropertyPlaceholderConfigurer to replace the ${..} tokens as Spring startup.

However, we can actually use PropertyPlaceholderConfigurer to inject references to different beans! So lets rewrite the user interface spring file:

<bean id="clientPropertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE">
<property name="location" value="com-acme.properties" />
</bean>
<bean id="ui" class="com.acme.UserInterface">
<property name="commandHandler" ref="${com.acme.connection}"/>
</bean>
<bean id="local" class="com.acme.CommandHandlerImpl"/>
<bean id="remote" class="org.codehaus.xfire.spring.remoting.XFireClientFactoryBean" lazy-init="true">
<!-- Assumes war is named web.war -->
<property name="wsdlDocumentUrl" value="http://localhost:8080/web/xfire.service?wsdl" />
<property name="serviceInterface" value="com.acme.CommandHandler" />
</bean>

And that's it! If we run the user interface with -Dcom.acme.connection=local then the bean named 'local' gets injected in, and everthing runs in the same standalone process. If we run the user interface with -Dcom.acme.connection=remote then the bean named 'remote' gets injected in, and we communicate with a our XFire web service.

Hope this helps :-)

0 comments: