This is part 4 of a series discussing a sample webapp that uses Spring WebMVC and Jetty to add a REST API to a standalone Java application. Part 1, Part 2, and Part 3 were posted previously.
This post discusses using Spring WebMVC for the client side, and also discusses some integration options for adding WebMVC to an existing application.
WebMVC Client
Of course, one of the largest motivations for a REST API is the ability to use it from any language, especially JavaScript in a browser. But there are times when consuming a REST API from Java can be very useful. HTTP is popular for client / server integration because firewalls are generally permissive, which is not true for Java RMI or most uses of JMS. Also, if an application must support a REST interface anyway, using that same REST interface for Java clients avoids the work of maintaining two remote interfaces. Of course, as in anything there are tradeoffs. Sending JSON over the wire is more verbose than a typical serialized Java object, and the latency introduced by marshalling and unmarshalling must be considered.
Spring WebMVC provides a class called RestTemplate
to simplify calling REST
APIs from Java. This follows a typical Spring design pattern where a complex
API is made more accessible through a template class (other examples include
JdbcTemplate
and JmsTemplate
).
Our example client uses Spring to instantiate the RestTemplate
, with this
XML configuration file:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<bean id="restTemplate"
class="org.springframework.web.client.RestTemplate">
<property name="messageConverters">
<list>
<bean
class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter"/>
</list>
</property>
</bean>
<bean id="client" class="org.anvard.webmvc.client.RestClient">
<property name="tmpl" ref="restTemplate"/>
<property name="host" value="localhost"/>
<property name="port" value="9999"/>
</bean>
</beans>
Unlike the server side discussed in Part 2, the client side does not use annotation-driven configuration, so we register the Jackson JSON conversion library explicitly.
The associated Java code is as follows:
package org.anvard.webmvc.client;
import org.anvard.webmvc.api.Calculation;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.web.client.RestTemplate;
public class RestClient {
private RestTemplate tmpl;
private String host;
private String port;
public Calculation calc(String op, int left, int right) {
return tmpl.getForObject(getUrlRoot() + "rest/calc/{op}/{left}/{right}", Calculation.class, op, left, right);
}
public Calculation calc2(Calculation in) {
return tmpl.postForObject(getUrlRoot() + "rest/calc2", in, Calculation.class);
}
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("/clientContext.xml");
RestClient client = (RestClient) ctx.getBean("client");
client.print(client.calc("add", 2, 2));
client.print(client.calc("subtract", 20, 2));
client.print(client.calc("multiply", 5, 3));
client.print(client.calc("divide", 20, 2));
client.print(client.calc2(new Calculation("add", 50, 50)));
client.print(client.calc2(new Calculation("subtract", 60, 40)));
client.print(client.calc2(new Calculation("multiply", 25, 12)));
client.print(client.calc2(new Calculation("divide", 16, 5)));
}
...
}
The print(...)
method and setter methods have been omitted for clarity.
The javadocs for RestTemplate
list other methods that are available;
here we display the two primary ones, getForObject
and postForObject
. Both
require a URL in string form as the first parameter. These URL strings support
path variables similar to what we saw on the server side in Part 2; however,
in this case the path variables are matched in order; the label in the curly brace
is not used.
The postForObject
method has an extra parameter inserted next, which is the
request body. Next is the expected type for the response body, followed by a
variable number of parameters that match to the path variables in the URL.
Obviously, each call to the RestTemplate
is making a network connection, so care
should be taken to make sure the call happens on a thread that can block without
slowing down the application, and to make sure that possible network or server
failures are handled.
Integration
This example shows adding a REST API to an existing Java SE application. For the application to be modular, it is necessary to separate the Spring WebMVC controller from other application classes, such as database access objects or server-side business logic. This raises issues with integration since the application may not be based on similar technology.
For WebMVC clients, this integration is simple. The example application uses
a Spring XML configuration file, but it is simple to replace this with a
direct instantiation of RestTemplate
wherever it will be used. The sample
Google Places client discussed in a previous post shows this.
For the server side, generally our goal will be to provide the Spring WebMVC controller class with a reference to application business logic. The business logic can get a reference to the controller class, but this is generally not necessary since as requests flow in from the client, the controller class is invoked automatically by Spring WebMVC.
For complete Spring applications, this integration can be done by combining
all beans into a single application context. Any existing application context
can be included from the WebMVC XML configuration file (since this file is read
automatically by Spring when the DispatcherServlet
is created). This approach
would break model-view-controller separation if we were writing a fully-fledged
web application, but since in this case we’re just adding a REST API on
existing business logic, it’s defensible from a design standpoint.
However, in many cases the application will not be Spring-based, or merging the application contexts may be undesirable. This makes integration more challenging. There are a few ways to proceed:
- A factory class can be used by Spring beans to instantiate or lookup objects
in the application. This can be done either directly in Java code or through
a Spring
FactoryBean
. - The Spring beans can register themselves in a separate registry which is used by the application to lookup the WebMVC controllers and inject application references.
- A Spring bean can be added that implements
ApplicationContextAware
to get a direct reference to the Spring application context and store it in a registry. This context can then be used to look up any desired bean by name or type. Here is an example. - While I have illustrated a
DispatcherServlet
and its ability to search for a Spring XML configuration file using a naming convention, it is also possible to use Spring’sContextLoaderListener
to load an application context in a servlet environment.
The last method deserves an expanded discussion. Unlike the
DispatcherServlet
, when the ContextLoaderListener
creates its application
context, it registers it as the root context. This means it’s possible to use
getWebApplicationContext(servletContext)
in WebApplicationContextUtils
to
retrieve it. (In the case of our embedded Jetty example, we can obtain the
servlet context easily from the WebAppContext
we instantiate.)
From a design standpoint, using a ContextLoaderListener
has the advantage
that it lets us use multiple instances of DispatcherServlet
to handle
separate web applications, each of which will have its own path and its own
Spring context, and all of which can access the ‘common’ beans in the root
context. The disadvantage is that we add some complexity to our configuration.
A good discussion of the difference can be found
here.
Any of these four methods will work. The first two methods allow us to avoid spreading Spring-related dependencies further in our code. The last method provides the most flexibility.
Summary
Through these posts we’ve seen how Spring WebMVC and Jetty can be combined to add a REST API to an existing application with very few lines of code, and without requiring a servlet container to be added to the architecture.