Wednesday, July 2, 2008

Simple Spring HTTP Remoting Example

I am using this blog entry to demonstrate by simple example the use of the Spring Framework's HTTP Remoting. There are numerous online resources on this subject, so my intention here is to provide an extremely simple but complete demonstration of using Spring's HTTP Remoting with non-browser clients.

The Spring approach to HTTP Remoting allows clients to communicate with the Spring-hosted server code via HTTP without the client code requiring any knowledge of HTTP being used. Instead, the client Java code only "sees" normal business-related Java objects (usually interfaces) rather than HTTP-specific objects.

Spring HTTP Remoting generally requires Spring and Java on both the server side and client side. However, if those two requirements can be met, Spring HTTP Remoting is easily applied.

The following steps allow HTTP communication between Spring-hosted clients and servers. After first briefly outlining the steps, I'll then delve into them in more detail (including code samples).


  1. Create or use an existing Spring bean that typically implements a Java interface.

    This is nothing special to HTTP remoting and is the same step you'd need to take to do most things in Spring (a notable exception is Spring JDBC that does not require any Spring beans to be used).


  2. Create the Spring XML configuration file for associating the bean created in step #1 with a Spring application context.

    As with Step #1, this XML file is nothing particular to Spring HTTP Remoting, but is instead common to nearly all Spring Framework wiring and configuration.


  3. Create or add to web.xml file.

    This third step is the first step that is more particular to Spring HTTP Remoting, but is still generally applicable with Spring MVC framework. This step includes adding the servlet class and URL mappings as one usually uses with Java EE servlets and JavaServer Pages. The most important part of this step is to specify the Spring DispatcherServlet. An optional "link" is also provided in this web.xml file to a context config location where one or more Spring XML application context files are located and used.


  4. Create the Spring-specific servlet context file.

    This XML file looks a lot like a "normal" Spring application context XML configuration file, but its name is prescribed by the convention of servlet name followed by a hypen and the word servlet. In other words, if the servlet was called "somewebthing" in the web.xml file, this Spring servlet configuration file would be called somewebthing-servlet.xml. This file contains the configuration for the HttpInvokerServiceExporter (the piece of this that is particular to the HTTP Remoting covered in this blog entry) and URL mapping information.


  5. Test!

    Although the simple client will be writting without HTTP in mind and will appear to only be using Java objects, it will actually be invoking the service via HTTP. This will be "proven" by running the client without the service deployed and watching for the resulting HTTP error code.




I'll now move onto demonstrating the above steps in greater detail and attempt to illustrate them concretely with code samples.

Step #1: The Bean and Its Interface

This step is no different than defining Java classes and interfaces they implement for use with Spring. The following code listings show the interface (StateCapitalServiceIF) and the implementing class (StateCapitalService) used for this example.

--- StateCapitalServiceIF.java ---

package examples.springhttp;

import java.io.Serializable;

/**
* The State Capital Service interface that the client will use to access
* server-side functionality via HTTP.
*/
public interface StateCapitalServiceIF extends Serializable
{
/**
* Provide capital of state whose name is provided.
*
* @param stateName Name of state whose capital is desired.
* @return Capital of the specified state; null if not found.
*/
public String getCapital(final String stateName);
}


--- StateCapitalService.java ---

package examples.springhttp;

import java.util.Map;

/**
* Implementation of functionality to be run after being called by client via
* HTTP.
*/
public class StateCapitalService implements StateCapitalServiceIF
{
Map statesAndCapitals = null;

public StateCapitalService()
{
}

/**
* Set my states to state capitals mapping.
*
* @param statesAndCapitals States to state capitals mapping.
*/
public void setStatesAndCapitals(final Map statesAndCapitals)
{
this.statesAndCapitals = statesAndCapitals;
}

/**
* Provide capital of state whose name is provided.
*
* @param stateName Name of state whose capital is desired.
* @return Capital of the specified state; null if not found.
*/
public String getCapital(final String stateName)
{
return this.statesAndCapitals.get(stateName);
}
}



Step #2: Spring Application Context Configuration File

I like to keep Spring's HTTP-specific configuration separate from the bean's XML configuration. Therefore, the bean's configuration is exactly like one would see normally with Spring. To configure the StateCapitalService class above, the following configuration is used:

--- spring-http-config.xml ---

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.5.xsd">

<util:map id="statesCapitalsMap">
<entry key="Alabama" value="Montgomery" />
<entry key="Alaska" value="Juneau" />
<entry key="Arizona" value="Phoenix" />
<entry key="Arkansas" value="Little Rock" />
<entry key="California" value="Sacramento" />
<entry key="Colorado" value="Denver" />
<entry key="Connecticut" value="Hartford" />
</util:map>

<bean id="stateCapitalService"
class="examples.springhttp.StateCapitalService"
p:statesAndCapitals-ref="statesCapitalsMap" />

</beans>


So far, nothing specific to HTTP Remoting has been done. In fact, the bean, its interface, and its XML application context configuration could all be run by a normal Java SE class like the one shown below:

--- MainServiceAppContext.java ---

package examples.springhttp;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
* Demonstrates how Spring bean can be used without any HTTP involvement.
*/
public class MainServiceAppContext
{
public static void printStateInfo(
final StateCapitalServiceIF stateCapitalMapper,
final String state)
{
System.out.println(
"The capital of " + state + " is "
+ stateCapitalMapper.getCapital(state));
}

/**
* @param args the command line arguments
*/
public static void main(String[] args)
{
final ApplicationContext context =
new ClassPathXmlApplicationContext(
"examples/springhttp/spring-http-config.xml" );
StateCapitalServiceIF stateCapitalMapper =
(StateCapitalServiceIF) context.getBean("stateCapitalService");
printStateInfo(stateCapitalMapper, "Alabama");
printStateInfo(stateCapitalMapper, "Colorado");
}
}


Step #3: The web.xml File

This web.xml file is familiar to anyone who has developed a Java EE web application. The web.xml used in this example is shown next.

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

<display-name>
Simple Spring HTTP Remoting Example
</display-name>

<description>
This is meant as an extremely simple example of using Spring's HTTP
Remoting capability.
</description>

<servlet>
<servlet-name>statesCapitals</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>statesCapitals</servlet-name>
<url-pattern>/statesCapitals</url-pattern>
</servlet-mapping>

<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>

<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/examples/springhttp/spring-http-config.xml</param-value>
</context-param>

</web-app>


Step #4: The Servlet Context Configuration File

Because the servlet in this example is named "statesCapitals," a Spring servlet configuration file named statesCapitals-servlet.xml needs to be provided. It is shown next:

--- statesCapitals-servlet.xml ---

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.5.xsd">

<bean id="httpStateCapitalService"
class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter"
p:service-ref="stateCapitalService">
<property name="serviceInterface">
<value>examples.springhttp.StateCapitalServiceIF</value>
</property>
</bean>

<bean id="urlMapping"
class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
<props>
<prop key="/statesCapitals">httpStateCapitalService</prop>
</props>
</property>
</bean>

</beans>


Step #5: Testing It

We need to configure the client to communicate via HTTP with our server-side application. The configuration for this is contained in spring-http-client-config.xml for this example and is shown next:

--- spring-http-client-config.xml ---

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.5.xsd">

<bean id="stateCapitalProxyService"
class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean">
<property name="serviceUrl">
<value>http://localhost:8080/SpringHTTPExample/statesCapitals</value>
</property>
<property name="serviceInterface">
<value>examples.springhttp.StateCapitalServiceIF</value>
</property>
</bean>

</beans>


The client code that uses the above XML to bootstrap a Spring container and call the server-side code via HTTP is in the class HttpClient and that code is shown next:

--- HttpClient.java ---

package examples.springhttp.client;

import examples.springhttp.StateCapitalServiceIF;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
* This class demonstrates a client of a Spring HTTP-exposed service and shows
* how the client interacts with the server as if using normal Java objects
* rather than using anything HTTP specific.
*/
public class HttpClient
{
public static void printStateInfo(
final StateCapitalServiceIF stateCapitalMapper,
final String state)
{
System.out.println(
"The capital of " + state + " is "
+ stateCapitalMapper.getCapital(state));
}

public static void main(final String[] arguments)
{
final ApplicationContext context =
new ClassPathXmlApplicationContext(
"examples/springhttp/client/spring-http-client-config.xml");
final StateCapitalServiceIF stateCapitalService =
(StateCapitalServiceIF) context.getBean("stateCapitalProxyService");
printStateInfo(stateCapitalService, "Colorado");
printStateInfo(stateCapitalService, "Alabama");
}
}


When the server-side code is built into a WAR and deployed to Tomcat or other web server, the above client will connect to it successfully and print out the capitals of Alabama and Colorado as shown in the next screen snapshot:



If the server-side application is undeployed or the web server is turned off, the client prints a 404 ("Not Found") HTTP status code indicating it is not available and proving that HTTP is being used. This is shown in the next screen snapshot.



The Packaging

I have displayed the contents of all the files above, but the packaging of them can be a little tricky. This section shows the contents of various JARs and the WAR I used to run the above examples.

--- SpringHTTPExample.war ---
This is the WAR file I deployed to Tomcat:

102 Thu Jul 03 23:25:54 MDT 2008 META-INF/MANIFEST.MF
1218 Thu Jul 03 23:13:42 MDT 2008 WEB-INF/web.xml
1505 Thu Jul 03 23:25:56 MDT 2008 WEB-INF/lib/SpringHTTPExample-web.jar
52915 Thu Jul 03 23:25:56 MDT 2008 WEB-INF/lib/commons-logging.jar
2832933 Thu Jul 03 23:25:56 MDT 2008 WEB-INF/lib/spring.jar
385822 Wed Jan 09 13:24:06 MST 2008 WEB-INF/lib/spring-webmvc.jar
1099 Thu Jul 03 22:33:04 MDT 2008 WEB-INF/examples/springhttp/spring-http-config.xml
1143 Thu Jul 03 23:25:42 MDT 2008 WEB-INF/statesCapitals-servlet.xml


In the above WAR, three of the JARs in the WEB-INF/lib are Spring-provided. Besides those, the remaining content of the WAR consists simply of the web.xml file shown above, the statesCapitals-servlet.xml file shown above, the spring-http-config.xml file shown above, and a JAR file called SpringHTTPExample-web.jar, whose contents are shown next.

--- SpringHTTPExample-web.jar ---

102 Thu Jul 03 23:25:54 MDT 2008 META-INF/MANIFEST.MF
1011 Thu Jul 03 23:25:54 MDT 2008 examples/springhttp/StateCapitalService.class
205 Thu Jul 03 23:25:54 MDT 2008 examples/springhttp/StateCapitalServiceIF.class


The contents of the JAR above consist simply of the bean class and the interface it implements, both of which are shown above.

--- SpringHTTPClient.jar ---
The contents JAR used to test the HTTP-exposed service are shown now:

102 Thu Jul 03 23:25:54 MDT 2008 META-INF/MANIFEST.MF
205 Thu Jul 03 23:25:54 MDT 2008 examples/springhttp/StateCapitalServiceIF.class
1618 Thu Jul 03 23:25:54 MDT 2008 examples/springhttp/client/HttpClient.class
954 Thu Jul 03 23:25:54 MDT 2008 examples/springhttp/client/spring-http-client-config.xml


The above client JAR includes the compiled version of the client class shown above along with the spring-http-client-config.xml configuration file shown above. Perhaps the most interesting thing to note here is that the interface class included in the server JAR also needs to be in the client JAR. This makes sense, of course, because it is that interface the client code uses as a proxy to the server side.

Conclusion

In this blog entry, I have included all the code (Java and XML) necessary to build and run a simple example that uses and illustrates Spring's HTTP Remoting capabilities. I did not cover every nuance of this feature and did not explicitly discuss some of the associations between various configuration files. There are several good resources on Spring's HTTP Remoting that do this. I instead attempted to illustrate complete code for a simple Spring HTTP Remoting example without referencing work on similar Spring remoting solutions. (Because Spring treats remoting very consistently, many good books and tutorials cover HTTP support with references to the other protocols supported.) I am including some references to these other resources related to Spring HTTP remoting below.

Other Resources

* Section 17.4 ("Exposing Services Using HTTP Invokers") of Spring Framework 2.5 Reference Manual

* Best of Both Worlds Remote Services with Spring HTTP Invoker

* A Case for Lightweight Remoting (Part 2): Spring to the Rescue

4 comments:

bytor99999 said...

Since you had the servletName-servlet.xml naming convention for the servlets Spring configuration file, I wanted to note that there is a naming convention too for the application context xml file for the ContextLoaderListener. It is looking for a file called applicationContext.xml by default. So if you have that file in the root of your classpath, then you don't need the context param declaration in your web.xml file

Dustin said...

bytor99999,

Thanks for pointing out that applicationContext.xml is the default used when the contextConfigLocation is not specified. I almost always have more than one file to include in realistic Spring applications and so must use the contextConfigLocation to include all of them. However, it would have been easier in this example to rely on the convention over configuration as you stated.

I'm leaving the example as-is to show the general case of specifying contextConfigLocation for context files of different names or for more than one file, but I appreciate you pointing out that there is a default available that is probably useful to many people in different situations.

Raju said...

Very nice article and it made spring remoting understanding simple..

Harshal said...

Well this is an old post but still I will take my chance...

Is it necessary to create a separate servlet to handle this because for that you have to create a stateCapitals-servlet.xml?