Spring 3 and RESTful Services
Introduction
This article describes the process for supporting REST (Representational State Transfer) in your application using Spring 3. For a variety of reasons including scalability, performance, and simplification, REST has gained critical mass as an alternative to SOAP-based Web Services and is now offered as part of JEE 6 in accordance with JSR-311. REST facilitates access to resources in a uniform way independent of the context or caller to promote simplified integration and facilitate re-use across architectural layers.
SpringMVC enhancements
Central to supporting REST in Spring 3 were enhancements made to SpringMVC and annotation driven controllers. In this article I present the hypothetical example of a motor vehicle distributor listing the types of vehicles that they sell. The HTTP GET method will be implemented in the controller for obtaining a list of vehicles or details of a specific vehicle and the output will be presented in the form of an object graph transformed to XML using XStream. XStream has been chosen as it is supported by the org.springframework.oxm.xstream.XStreamMarshaller class in the Spring Object/XML Mapping (OXM) package an does not require custom classes to marshall/unmarshall a payload.
Getting started
The quickest way to create the application structure for this example is with Maven. Here, we will use the web archetype to create an artifact deployable to our Tomcat servlet container.
mvn archetype:generate -DgroupId=net.comdynamics.springrestexample -DartifactId=springrestexample-web -DpackageName=net.comdynamics.springrestexample -Dversion=0.1-SNAPSHOT -DarchetypeArtifactId=maven-archetype-webapp
The directory structure will be similar to the following. You will need to create the Java classes and resources listed in this article as part of the steps listed.
Maven project
I have specified a property in the Maven POM file to indicate the use of Spring 3.0 Release 3.
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>net.comdynamics.springrestexample</groupId>
<artifactId>springrestexample-web</artifactId>
<packaging>war</packaging>
<version>0.1-SNAPSHOT</version>
<name>springrestexample-web Maven Webapp</name>
<url>http://maven.apache.org</url>
<repositories>
<repository>
<id>maven2-repository.dev.java.net</id>
<name>Java.net Repository for Maven</name>
<url>http://download.java.net/maven/2</url>
<layout>default</layout>
</repository>
<repository>
<id>spring-milestone</id>
<url>http://maven.springframework.org/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<properties>
<spring.version>3.0.3.RELEASE</spring.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-oxm</artifactId>
<version>${spring.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
<version>1.2.2</version>
<optional>false</optional>
</dependency>
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.14</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
</plugins>
<finalName>springrestexample</finalName>
</build>
</project>
JEE deployment descriptor
In the web.xml, the Spring bean configuration is referenced from applicationContext.xml. The Spring MVC DispatcherServlet is responsible for dispatching HTTP requests to our handler which in this scenario is the VehicleController class. Specifying with an asterisk ensures all incoming HTTP requests are processed by the Dispatcher.
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
id="WebApp_ID" version="2.5">
<display-name>springrestexample</display-name>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:/applicationContext.xml
</param-value>
</context-param>
<context-param>
<param-name>log4jConfigLocation</param-name>
<param-value>/WEB-INF/classes/log4j.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>springrestexample</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value></param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>springrestexample</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
Spring Application Context
The Spring application context wires the OXM XStream marshaller and handler. The context:component-scan element triggers annotation-based autowiring against the package specified.
<?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:context="http://www.springframework.org/schema/context"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
<context:component-scan base-package="net.comdynamics.springrestexample"/>
<bean id="xstreamMarshaller" class="org.springframework.oxm.xstream.XStreamMarshaller"/>
<bean id="marshallingHttpMessageConverter"
class="org.springframework.http.converter.xml.MarshallingHttpMessageConverter">
<property name="marshaller" ref="xstreamMarshaller"/>
<property name="unmarshaller" ref="xstreamMarshaller"/>
</bean>
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
<property name="messageConverters">
<util:list id="beanList">
<ref bean="marshallingHttpMessageConverter"/>
</util:list>
</property>
</bean>
<mvc:annotation-driven/>
</beans>
Domain Object
The Vehicle class is an immutable domain object that encapsulates id, make, and model fields.
package net.comdynamics.springrestexample.domain;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class Vehicle {
private static final Log log = LogFactory.getLog(Vehicle.class);
private Long id;
private String make;
private String model;
public Vehicle(Long id, String make, String model) {
this.id = id;
this.make = make;
this.model = model;
}
public Long getId() {
return id;
}
public String getMake() {
return make;
}
public String getModel() {
return model;
}
@Override
public boolean equals(Object obj) {
if (obj == this)
return true;
if (this.id == null || !(obj instanceof Vehicle))
return false;
Vehicle vehicle = (Vehicle) obj;
return this.id.equals(vehicle.getId());
}
@Override
public int hashCode() {
return this.id == null ? 1 : this.id.hashCode();
}
}
Service Component
There is no reliance on a data tier in this example therefore I have created a service component which uses an in-memory collection of vehicles. The service exposes two methods: getVehicleById() and getAllVehicles(). These methods are called by the Controller class when incoming HTTP GET requests are processed by the Dispatcher.
package net.comdynamics.springrestexample.service;
import net.comdynamics.springrestexample.domain.Vehicle;
import java.util.*;
import javax.inject.Named;
@Named("vehicleService")
public class VehicleService {
// Dummy list of vehicles
static final Map<Long, Vehicle> vehicles = new HashMap<Long, Vehicle>();
// Dummy entry to be returned if not found
static final Vehicle notFound = new Vehicle(0L, "Vehicle", "Not Found");
static {
vehicles.put(1L, new Vehicle(1L, "Audi", "A1"));
vehicles.put(2L, new Vehicle(2L, "Audi", "S4"));
vehicles.put(3L, new Vehicle(3L, "Audi", "RS5"));
vehicles.put(4L, new Vehicle(4L, "BMW", "120i"));
vehicles.put(5L, new Vehicle(5L, "BMW", "330d"));
vehicles.put(6L, new Vehicle(6L, "BMW", "M3"));
vehicles.put(7L, new Vehicle(7L, "Ford", "Focus"));
vehicles.put(8L, new Vehicle(8L, "Ford", "Mondeo"));
vehicles.put(9L, new Vehicle(9L, "Volkswagen", "Polo TSI"));
vehicles.put(10L,new Vehicle(10L, "Volkswagen", "Golf GTI"));
vehicles.put(11L,new Vehicle(11L, "Volkswagen", "Tiguan"));
}
public Vehicle getVehicleById(Long vehicleId) {
if (vehicleId < vehicles.size() + 1) {
return vehicles.get(vehicleId);
} else {
// Return not found
return notFound;
}
}
/**
* Return all vehicles as a List.
*/
public List<Vehicle> getAllVehicles() {
return new ArrayList<Vehicle>(vehicles.values());
}
}
The Controller
The use of the JSR 330 annotation @Named facilitates auto-wiring of dependencies as this controller requires the VehicleService. Here, the annotation @RequestMapping is used to map a URI for a resource and the HTTP method supported which is this instance is GET. An HTTP request to http://server:port/springrestexample/vehicles/ will list all vehicles, and http://server:port/springrestexample/vehicles/3 will return the third vehicle in the motor vehicle distributors inventory. I have chosen to use the plural name (“vehicles”) and not the singular name (“vehicle”) in mappings as this approach is widely adopted for resources accessed using REST.
The @ResponseBody annotation ensures that the return type is written to the response HTTP body which in our case is handled by the XStream OXM class.
package net.comdynamics.springrestexample.rest;
import net.comdynamics.springrestexample.domain.Vehicle;
import net.comdynamics.springrestexample.service.VehicleService;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.inject.Inject;
import javax.inject.Named;
import java.util.List;
@Controller
public class VehicleController {
private static final Log log = LogFactory.getLog(VehicleController.class);
@Inject
@Named("vehicleService")
private VehicleService vehicleService;
/**
* Get all vehicles.
*/
@RequestMapping(value = "/vehicles", method = RequestMethod.GET)
@ResponseBody
public List<Vehicle> getAllVehicles() {
return vehicleService.getAllVehicles();
}
/**
* Get the vehicle matching a specific id.
*
* @param vehicleId the identifier of the requested vehicle
*/
@RequestMapping(value = "/vehicles/{vehicleId}", method = RequestMethod.GET)
@ResponseBody
public Vehicle getVehicleById(@PathVariable Long vehicleId) {
Vehicle vehicle = vehicleService.getVehicleById(vehicleId);
log.debug("Got request for vehicle " + vehicleId + ", returning " + vehicle);
return vehicle;
}
}
Accessing resources via a web brower
Using a browser to get details for vehicle with an identifier of 10 at http://localhost:8080/springrestexample/vehicles/10 will produce the following:
When requesting all vehicles at http://localhost:8080/springrestexample/vehicles/ you should see the following:
Conclusion
Support for REST in Spring 3 is provided using a SpringMVC controller that maps a URI to a resource and HTTP request method type. Future articles will examine the use of Spring URI REST templates and the Content Negotiating View Resolver support for different response types based on the user-agent accessing the resource.





