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;When I run this in standalone, I can wire everything like:
public class CommandHandlerImpl implements CommandHandler {
public String handleCommand(String command) {
if ("greet".equals(command)) {
return "hello";
} else {
return "unrecognised command";
}
}
}
<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:
Post a Comment