Introduction
This article provides an introduction to Spring’s REST services. The first section of the article provides a good introductory knowledge to the basics of REST services following by a sample application. The next section extends the introductory part by providing a bit complicated implementation by supporting the various protocol methods exposed and supported by the service. Later on, the article goes on in explaining the RestTemplate API , which is the client interface template for accessing RESTful services. The final section of this article provides a sample thereby integrating the RESTful services in the Spring’s Web Tier. If you are beginner looking for basic concepts on Spring Framework, please read Introduction to Spring Framework.
also read:
Download Spring REST Services Sample Code
- [download id=”8″]
Simple REST Service
We will see a brief introduction on RESTful services before proceeding with an example. REST is another way of programming paradigm where a system is modeled by looking it in terms of resources and then imposing operations on them. Identified resources can then be accessed by providing Unique Resource Identifier (URL) to them. For example, let us say in a Customer Management system, where customer being the primary entity, the identified resource can be a ‘Customer’ object. This resource will be given a URL to uniquely identify it, example, ‘/customerMgmt/customer’. Then coming to the list of operations applicable to a customer object like addition of a new customer, deleting an existing customer, updating existing customer and retrieving all the customers, the operations can be modeled and represented through URLS like ‘/customerMgmt/add’, ‘/customerMgmt/delete’, ‘/customerMgmt/remove’, ‘/customerMgmt/list’. Note that HTTP provides various methods like GET, PUT, POST and DELETE. In most of the cases, it is simple to map the business operations to any one of the corresponding HTTP methods. For example, in this case, ‘customerList’ operation can be mapped to a ‘GET’ request as ‘GET’ method is expected to return some meaningful data to the client. For ‘customerAdd’ operation, it is good to designate this as ‘PUT’ as we create a new customer object in the server. Similarly ‘DELETE’ method can be designated for ‘customerDelete’ method.
The next thing to be discussed is the list of formats/protocols supported by the service. It is common these days that a service to support the standard and the portable XML format. However, the implementation can provide support to other formats like HTML, JSON etc. Initially Sun came with a specification for designing RESTful webservice through JAX-WS (JAX for Web Services) and an implementation for the same is provided by Jersey. In this section, we will develop a simple sample application called Employee Management by making use of Jersey implementation.
So the first step would be to identify the model objects so that they can be represented as resources. In our simple example, this could be an Employee object. Have a look at the class declaration.
package net.javabeat.articles.spring.rest.simple; public class Employee { private String id; private String name; private String department; public Employee(){} public Employee(String id, String name, String department){ this.id = id; this.name = name; this.department = department; } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getDepartment() { return department; } public void setDepartment(String department) { this.department = department; } public String toString(){ StringBuilder result = new StringBuilder(); result.append("[employee]"); result.append("[id]" + id + "[id]"); result.append("[name]" + name + "[name]"); result.append("[department]" + department + "[department]"); result.append("[employee]"); return result.toString(); } public String toXmlString(){ StringBuilder result = new StringBuilder(); result.append(""); result.append("" + id + ""); result.append("" + name + ""); result.append("" + department + ""); result.append(""); return result.toString(); } }
Note that we have used toString() and toXmlString() methods to format the employee object. We will see the significance of this method in the later section.
Let us assume we provide one single service method to the client where they can query the list of available employees. For better illustration, we will support two overloaded versions of this method, one that returns the response in XML format and the other one that returns the response in PLAIN text format. Note that we need to define a separate service that will do these business operations. The complete code listing is given below,
package net.javabeat.articles.spring.rest.simple; import java.util.HashSet; import java.util.Iterator; import java.util.Set; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; @Path("employee-manager") public class EmployeeManager { private static Set employees; @GET @Produces(MediaType.TEXT_XML) @Path("employeelist") public String getEmployeesInXmlFormat(){ StringBuilder xmlResult = new StringBuilder(); xmlResult.append(""); Iterator iterator = employees.iterator(); while (iterator.hasNext()){ Employee employee = iterator.next(); String employeeAsXml = employee.toXmlString(); xmlResult.append(employeeAsXml); } xmlResult.append(""); return xmlResult.toString(); } @GET @Produces(MediaType.TEXT_PLAIN) @Path("employeelist") public String getEmployeesInPlainFormat(){ StringBuilder plainResult = new StringBuilder(); plainResult.append("[employees]"); Iterator iterator = employees.iterator(); while (iterator.hasNext()){ Employee employee = iterator.next(); String employeeAsXml = employee.toXmlString(); plainResult.append(employeeAsXml); } plainResult.append("[employees]"); return plainResult.toString(); } static{ employees = new HashSet(); employees.add(new Employee("1", "David", "IT")); employees.add(new Employee("2", "John", "SALES")); } }
Note that this class makes use of JAX-WS APIs for designating this class as a service class. We will see them in more details in this section. The first significant inclusion is the ‘@Path’ annotation to the class which provides a path to the client to access the service. For example, let us say that this service is deployed in a web container in the context path ’employeeTest’. Now the path to access the employee service will be ’employeeTest/employee-manager’. Next we have implemented two business methods ‘getEmployeesInXmlFormat’ and ‘getEmployeesInPlainFormat’. The annotation ‘@GET’ provides an indication that this business method is expected to serve clients by sending data to it, in our case it will fetch and return the list of available employee objects. Though it is possible for the implementation to add/update/remove a customer object here, it is generally considered as a very bad programming practice.
The next annotation is the ‘@Produces’ which defines the type/format of output that this method will produce. If the method is expected to send an xml/plain response, it can use ‘MediaType.TEXT_XML’, ‘MediaType.TEXT_PLAIN’ respectively. The annotation ‘@Path’ can also be defined at the method level, this indicate that if we want to access the ‘GET’ business method ‘getEmployeesInXmlFormat’, then the path would be ’employeeTest/employee-manager/employeelist’ which will return an XML response.
We will see the deployment descriptor in this section. The listing for this is given below.
<?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>REST-EmployeeManager</display-name> <servlet> <servlet-name>REST-Simple</servlet-name> <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class> <init-param> <param-name>com.sun.jersey.config.property.packages</param-name> <param-value>net.javabeat.articles.spring.rest.simple</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>REST-Simple</servlet-name> <url-pattern>/rest-simple/*</url-pattern> </servlet-mapping> </web-app>
Since we are using Jersey implementation, you can see Jersey specific classes in the deployment descriptor. Note that the property ‘com.sun.jersey.config.property.packages’ takes the name of the package and it is a better practice to organize the resource in a single package. Deploy the application in a Web Container (like Tomcat) and try accessing the following URL
http://localhost:8080/REST-EmployeeManager/rest-simple/employee-manager/employeelist
Listing of all employees
In this section, we will make use of Jersey Client APIs for directly accessing the services RESTful from a stand-alone program. Have a look at the following code.
package net.javabeat.articles.spring.rest.simple; import java.net.URI; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.UriBuilder; import com.sun.jersey.api.client.Client; import com.sun.jersey.api.client.WebResource; import com.sun.jersey.api.client.config.ClientConfig; import com.sun.jersey.api.client.config.DefaultClientConfig; public class EMClient { public static void main(String[] args) throws Exception{ ClientConfig clientConfig = new DefaultClientConfig(); Client clientObject = Client.create(clientConfig); String uriPath = "http://localhost:8080/REST-Simple/rest-simple"; URI uriObject = UriBuilder.fromUri(uriPath).build(); WebResource employeeResource = clientObject.resource(uriObject); xmlFormatTest(employeeResource); plainFormatTest(employeeResource); } private static void xmlFormatTest(WebResource employeeResource){ WebResource innerResource = employeeResource.path("employee-manager").path("employeelist"); WebResource.Builder builderObject = innerResource.accept(MediaType.TEXT_XML); String employeesAsXml = builderObject.get(String.class); System.out.println(employeesAsXml); } private static void plainFormatTest(WebResource employeeResource){ WebResource innerResource = employeeResource.path("employee-manager").path("employeelist"); WebResource.Builder builderObject = innerResource.accept(MediaType.TEXT_PLAIN); String employeesAsPlainText = builderObject.get(String.class); System.out.println(employeesAsPlainText); } }
Note that we have constructed and represented the URL object and have accessed the service that returns the response in plain and in xml format.
RESTful services – GET/POST/DELETE
We will illustrate in this section the design of RESTful services that makes use GET, POST and DELETE http designator methods. We will develop a simple contact management application that illustrates the management of contacts. The definition of the model class ‘Contact’ is given below. As one can see, the definition is pretty simple and it defines various properties such as ‘id’, ‘name’, ’emailId’ and ‘phoneNumber’.
package net.javabeat.articles.spring.rest.contacts; public class Contact { private String id; private String name; private String emailId; private String phoneNumber; public Contact(){} public Contact(String id, String name){ this.id = id; this.name = name; } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getEmailId() { return emailId; } public void setEmailId(String emailId) { this.emailId = emailId; } public String getPhoneNumber() { return phoneNumber; } public void setPhoneNumber(String phoneNumber) { this.phoneNumber = phoneNumber; } public static Contact contact(String id, String name, String emailId, String phoneNumber){ Contact contact = new Contact(); contact.setId(id); contact.setName(name); contact.setEmailId(emailId); contact.setPhoneNumber(phoneNumber); return contact; } }
Now, let us define a separate service which we will define the various business operations that can be performed on the contacts. The code listing for the same is given below,
package net.javabeat.articles.spring.rest.contacts; import java.util.Set; public interface ContactService { Set getAllContacts(); Contact getContactById(String contactId); Set getContactsByName(String contactName); Contact newContact(String name, String emailId, String phoneNumber); boolean deleteContact(String id); }
Note that this interface provides various business operations like retrieving the list of contacts, retrieving a particular contact through id and name, creating and deleting a contact. The implementation for the corresponding service is given below,
package net.javabeat.articles.spring.rest.contacts; import java.util.Collections; import java.util.Date; import java.util.HashSet; import java.util.Iterator; import java.util.Set; public class ContactServiceImpl implements ContactService { private static Set contacts; @Override public Set getAllContacts() { return Collections.unmodifiableSet(contacts); } @Override public Contact getContactById(String contactId) { Set allContacts = getAllContacts(); Iterator iterator = allContacts.iterator(); while (iterator.hasNext()){ Contact aContact = iterator.next(); if (contactId.equals(aContact.getId())){ return aContact; } } return null; } @Override public Set getContactsByName(String contactName){ Set allContacts = getAllContacts(); Set returnContacts = new HashSet(); Iterator iterator = allContacts.iterator(); while (iterator.hasNext()){ Contact aContact = iterator.next(); if (contactName.equals(aContact.getName())){ returnContacts.add(aContact); } } return returnContacts; } public Contact newContact(String name, String emailId, String phoneNumber){ Contact contact = new Contact(); contact.setName(name); contact.setEmailId(emailId); contact.setPhoneNumber(phoneNumber); contact.setId(new Date().toString()); contacts.add(contact); return contact; } public boolean deleteContact(String id){ Contact contactToBeRemoved = null; Set allContacts = getAllContacts(); Iterator iterator = allContacts.iterator(); while (iterator.hasNext()){ Contact aContact = iterator.next(); if (aContact.getId().equals(id)){ contactToBeRemoved = aContact; } } if (contactToBeRemoved != null){ contacts.remove(contactToBeRemoved); return true; }else{ return false; } } static{ contacts = new HashSet(); contacts.add(contact("1", "Henry", "[email protected]", "123456789")); contacts.add(contact("2", "Alfred", "[email protected]", "212345678")); contacts.add(contact("3", "Martin", "[email protected]", "312345678")); contacts.add(contact("4", "Martin", "[email protected]", "412345678")); } private static Contact contact(String id, String name, String emailId, String phoneNumber){ return Contact.contact(id, name, emailId, phoneNumber); } }
Note that the implementation maintains an in-memory collection for storing the contact objects. However, it is possible to extend the implementation by storing the contact objects in a reliable persistent store like a database. Later in this section, we will see that the example uses formatting contacts in xml and in plain format. So, it is good to externalize the formatting code and a separate utility class is created that does the same job whose implementation is given below,
package net.javabeat.articles.spring.rest.contacts; import java.util.Iterator; import java.util.Set; public class ContactsFormatter { public static String formatContactsInXml(Set allContacts){ StringBuilder xmlResult = new StringBuilder(); xmlResult.append(""); Iterator iterator = allContacts.iterator(); while (iterator.hasNext()){ Contact aContact = iterator.next(); String contactAsXml = formatContactInXml(aContact); xmlResult.append(contactAsXml); } xmlResult.append(""); return xmlResult.toString(); } public static String formatContactInXml(Contact contact){ StringBuilder result = new StringBuilder(); result.append(""); result.append("" + contact.getId() + ""); result.append("" + contact.getName() + ""); result.append("" + contact.getEmailId() + ""); result.append("" + contact.getPhoneNumber() + ""); result.append(""); return result.toString(); } public static String formatNullContactInXml(String contactId){ StringBuilder result = new StringBuilder(); result.append(""); result.append("EMPTY CONTACT FOR " + contactId); result.append(""); return result.toString(); } public static String formatContactsInPlain(Set allContacts){ StringBuilder xmlResult = new StringBuilder(); xmlResult.append("[contacts]"); Iterator iterator = allContacts.iterator(); while (iterator.hasNext()){ Contact aContact = iterator.next(); String contactAsXml = formatContactInPlain(aContact); xmlResult.append(contactAsXml); } xmlResult.append("[contacts]"); return xmlResult.toString(); } public static String formatContactInPlain(Contact contact){ StringBuilder result = new StringBuilder(); result.append("[contact]"); result.append("[id]" + contact.getId() + "[id]"); result.append("[name]" + contact.getName() + "[name]"); result.append("[emailId]" + contact.getEmailId() + "[emailId]"); result.append("[phoneNumber]" + contact.getPhoneNumber() + "[phoneNumber]"); result.append("[contact]"); return result.toString(); } }
Note that the above class provides variety of utility methods for formatting contact objects in plain and xml formats. Now, we will come to the core class which we will used by the client and that simply act as a delegate in delegating the calls to the service objects. Have a look at the class definition below.
package net.javabeat.articles.spring.rest.contacts; import java.util.Set; import javax.ws.rs.DELETE; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.MediaType; @Path("contact-manager") public class ContactManager { private ContactService contactSevice; public ContactManager(){ contactSevice = new ContactServiceImpl(); } @GET @Produces(MediaType.TEXT_XML) @Path("contacts") public String getContactsInXmlFormat(){ Set allContacts = contactSevice.getAllContacts(); return ContactsFormatter.formatContactsInXml(allContacts); } @GET @Produces(MediaType.TEXT_PLAIN) @Path("contacts") public String getContactsInPlainFormat(){ Set allContacts = contactSevice.getAllContacts(); return ContactsFormatter.formatContactsInPlain(allContacts); } @GET @Produces(MediaType.TEXT_XML) @Path("contacts/{contactId}") public String getContactById(@PathParam("contactId") String contactId){ Contact contact = contactSevice.getContactById(contactId); if (contact == null){ return ContactsFormatter.formatNullContactInXml(contactId); }else{ return ContactsFormatter.formatContactInXml(contact); } } @GET @Produces(MediaType.TEXT_XML) @Path("contacts/contact") public String getContactByName(@QueryParam("contactName") String contactName){ Set allContacts = contactSevice.getContactsByName(contactName); if (allContacts == null || allContacts.isEmpty()){ return ContactsFormatter.formatNullContactInXml(contactName); }else{ return ContactsFormatter.formatContactsInXml(allContacts); } } @POST @Produces(MediaType.TEXT_XML) @Path("contacts/newContact") public String newContact( @QueryParam("contactName") String contactName, @QueryParam("contactEmailId") String contactEmailId, @QueryParam("contactPhoneNumber") String contactPhoneNumber){ Contact createdContact = contactSevice.newContact(contactName, contactEmailId, contactPhoneNumber); return ContactsFormatter.formatContactInXml(createdContact); } @DELETE @Path("contacts/deleteContact/{contactId}") @Produces(MediaType.TEXT_PLAIN) public String deleteContact(@PathParam("contactId") String contactId){ boolean deleteResult = contactSevice.deleteContact(contactId); if (deleteResult){ return "SUCCESS"; }else{ return "FAILURE"; } } }
There are lot many additions to this manager class. First of all, it illustrates the usage of update operations on the server through its POST and DELETE http methods. Secondly it illustrates the usage of two new annotations which are ‘QueryParam’ and ‘PathParam’. We will see the significance of these annotations here.
Let us say, in the URL ‘A/B/C?p=q’, there is a query parameter ‘p’ and the value for the parameter is ‘q’. Now if such a URL having query parameters, reaching a business method, then it is possible to extract the query parameter using the ‘@QueryParam’ annotation. For example, in the business method ‘getContactByName’, we have designated the method level parameter ‘contactName’ using the ‘@QueryParam’ annotation. This means that the URL ‘contact-manager/contacts/contact’ hitting the business method getContactByName() is expected to have a query parameter ‘contactName’ with a valid contact name like ‘contact-manager/contacts/contact?contactName=testContact’ and the value for the parameter ‘contactName’ will get injected into the method-level variable ‘contactName’.
Now, let us see the significance of the annotation ‘@PathParam’. This is similar to ‘QueryParam’ in that in extracts the information from the URL path. For example, in the business method ‘getContactById’ we have designated the method-level variable with ‘contactId’ with ‘@PathParam’ annotation. Also the method has ‘@Path’ annotation that has the value ‘contacts/{contactId}’ which means that this method can be accessed
‘/contacts/1’, ‘/contacts/2’ like that. The value ‘1’ or ‘2’ is a part of the URL path and hence a path parameter will be injected to the variable method-level ‘contactId’.
Deploy the application in a Web Container and try accessing the application with the following URLs
- http://localhost:8080/REST-Contacts/rest-contacts/contact-manager/contacts
- http://localhost:8080/REST-Contacts/rest-contacts/contact-manager/contacts/1
- http://localhost:8080/REST-Contacts/rest-contacts/contact-manager/contacts/contact?contactName=Martin
Details of a contact with contact id as ‘1’
Listing all the contacts with name equal to ‘Martin’
We will also illustrate the usage of directly accessing the client in the section, Go through the following code sample,
package net.javabeat.articles.spring.rest.contacts; import java.net.URI; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.UriBuilder; import com.sun.jersey.api.client.Client; import com.sun.jersey.api.client.WebResource; import com.sun.jersey.api.client.config.ClientConfig; import com.sun.jersey.api.client.config.DefaultClientConfig; import com.sun.jersey.core.util.MultivaluedMapImpl; public class ContactClient { public static void main(String[] args) throws Exception{ ClientConfig clientConfig = new DefaultClientConfig(); Client clientObject = Client.create(clientConfig); String uriPath = "http://localhost:8080/REST-Contacts/rest-contacts/contact-manager"; URI uriObject = UriBuilder.fromUri(uriPath).build(); WebResource contactResource = clientObject.resource(uriObject); testGETRequests(contactResource); testPOSTRequests(contactResource); testDELETERequests(contactResource); } private static void testGETRequests(WebResource contactResource){ testGETAllContactsXml(contactResource); testGETAllContactsPlain(contactResource); testGETAContactXml(contactResource); testGETContactsByNameXml(contactResource); } private static void testGETAllContactsXml(WebResource contactResource){ WebResource innerResource = contactResource.path("contacts"); WebResource.Builder builderObject = innerResource.accept(MediaType.TEXT_XML); String contactsAsXml = builderObject.get(String.class); System.out.println(contactsAsXml); } private static void testGETAllContactsPlain(WebResource contactResource){ WebResource innerResource = contactResource.path("contacts"); WebResource.Builder builderObject = innerResource.accept(MediaType.TEXT_PLAIN); String contactsAsPlainText = builderObject.get(String.class); System.out.println(contactsAsPlainText); } private static void testGETAContactXml(WebResource contactResource){ MultivaluedMap parameters = new MultivaluedMapImpl(); parameters.add("contactName", "Henry"); WebResource innerResource = contactResource.path("contacts").path("contact").queryParams(parameters); WebResource.Builder builderObject = innerResource.accept(MediaType.TEXT_XML); String contactsAsPlainText = builderObject.get(String.class); System.out.println(contactsAsPlainText); } private static void testGETContactsByNameXml(WebResource contactResource){ MultivaluedMap parameters = new MultivaluedMapImpl(); parameters.add("contactName", "Henry"); WebResource innerResource = contactResource.path("contacts").path("2"); WebResource.Builder builderObject = innerResource.accept(MediaType.TEXT_XML); String contactsAsPlainText = builderObject.get(String.class); System.out.println(contactsAsPlainText); } private static void testPOSTRequests(WebResource contactResource){ testPOSTAddContact(contactResource); } private static void testPOSTAddContact(WebResource contactResource){ MultivaluedMap parameters = new MultivaluedMapImpl(); parameters.add("contactName", "Test-Name"); parameters.add("contactEmailId", "Test-EmailId"); parameters.add("contactPhoneNumber", "Test-PhoneNumber"); WebResource innerResource = contactResource.path("contacts").path("newContact").queryParams(parameters); WebResource.Builder builderObject = innerResource.accept(MediaType.TEXT_XML); String contactsAsPlainText = builderObject.post(String.class); System.out.println(contactsAsPlainText); } private static void testDELETERequests(WebResource contactResource){ testDELETERemoveContact(contactResource); } private static void testDELETERemoveContact(WebResource contactResource){ WebResource innerResource = contactResource.path("contacts").path("deleteContact").path("1"); WebResource.Builder builderObject = innerResource.accept(MediaType.TEXT_XML); String result = builderObject.delete(String.class); System.out.println("Delete result is: " + result); } }
The code looks similar to the one that we saw in the last section expect that for DELETE and POST requests, it uses the ‘delete’ and ‘post’ methods. Also it uses the method ‘queryParams’ for sending in a list of query parameters in the ‘testGETAContactXml’ method.
Spring’s REST Template
Now that we have a good hands-on what REST services are and how they can be modeled and accessed, it’s time to see the RestTemplate interface from Spring that acts a client API for accessing RESTful services. For the server side design of the services, we will look into it in the next section.
RestTemplate encapsulates the functionality of accessing the REST based services and hides much of the complexity like the conversion of http messages to the required format as well as error handling.
package net.javabeat.articles.spring.resttemplate; import java.util.HashMap; import java.util.Map; import org.springframework.web.client.RestTemplate; public class GetTest { public static void main(String[] args) { testSimpleGetForObject(); testSimpleGetForObjectWithParams(); } private static void testSimpleGetForObject(){ RestTemplate template = new RestTemplate(); String allContacts = template.getForObject( "http://localhost:8080/REST-Contacts/rest-contacts/contact-manager/contacts", String.class); System.out.println("AllContacts are " + allContacts); } private static void testSimpleGetForObjectWithParams(){ RestTemplate template = new RestTemplate(); Map parameters = new HashMap(); parameters.put("contactId", "2"); String contacts = template.getForObject( "http://localhost:8080/REST-Contacts/rest-contacts/contact-manager/contacts/{contactId}", String.class, parameters); System.out.println("Contacts result is " + contacts); } }
Have a look at the above code sample. RestTemplate objects can be instantiated directly the clients. Optionally, clients can configure a list of converters to the template object for converting any custom objects. For sending a ‘GET’ request, it can use the ‘getForObject’ method which accepts the URL and the expected return type as a class object. To pass parameters to the ‘GET’ request, we can use the overloaded version of the ‘getForObject’ method which takes the parameters map as the third argument. Note that any template parameter in the URL (in the form {parameterName}) will be substituted with the values taken from the parameters map if such a parameter is present.
Integrating Rest on Spring MVC
In the final section of the article, we will see the usage of RESTful services in the Web tier. In this section, we will build a simple device management application for managing a list of devices. We will keep the functionality of this example as simple as possible as this is merely for illustration purposes. Have a look at the Device model class. This class defines the properties ‘id’, ‘name’, ‘type’ and ‘manufacturer’. Note that the annotation ‘@XmlRootElement’ is used, we will see the significance of this annotation later in the section.
package net.javabeat.articles.rest.dm.model; import javax.xml.bind.annotation.XmlRootElement; @XmlRootElement(name = "device") public class Device { private String id; private String name; private String type; private String manufacturer; public Device(){} public Device(String id, String name){ this.id = id; this.name = name; } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getType() { return type; } public void setType(String type) { this.type = type; } public String getManufacturer() { return manufacturer; } public void setManufacturer(String manufacturer) { this.manufacturer = manufacturer; } public String toString(){ return id + "-" + name + "-" + type + "-" + manufacturer; } }
We have also modeled the devices collection as a separate class whose definition is given below. Note that this class also uses the annotation ‘@XmlRootElement’.
package net.javabeat.articles.rest.dm.model; import java.util.Set; import javax.xml.bind.annotation.XmlRootElement; @XmlRootElement(name = "devices") public class DeviceCollection { private Set allDevices; public DeviceCollection(){ } public Set getAllDevices() { return allDevices; } public void setAllDevices(Set allDevices) { this.allDevices = allDevices; } }
The following listing shows the definition for device service. Note that it provides the service methods for adding and retrieving the devices.
package net.javabeat.articles.rest.dm.service; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; import net.javabeat.articles.rest.dm.model.Device; public class DeviceServiceImpl { private static Map deviceCollection; public void addDevice(Device device){ String id = device.getId(); if (id == null){ id = new Date().toString(); device.setId(id); } deviceCollection.put(id, device); } public Set getAllDevices(){ return new HashSet(deviceCollection.values()); } public Device getDevice(String deviceId){ Iterator<Map.Entry> iterator = deviceCollection.entrySet().iterator(); while (iterator.hasNext()){ Map.Entry entry = iterator.next(); if (deviceId.equals(entry.getKey())){ return entry.getValue(); } } return null; } static{ deviceCollection = new HashMap(); Device testDevice1 = new Device("1", "High Definition Audio Codec"); testDevice1.setManufacturer("Controllers"); testDevice1.setType("IDT"); deviceCollection.put(testDevice1.getId(), testDevice1); Device testDevice2 = new Device("2", "Generic PNP Monitor"); testDevice2.setManufacturer("XYZ"); testDevice2.setType("Monitors"); deviceCollection.put(testDevice2.getId(), testDevice2); Device testDevice3 = new Device("3", "2 GB Processor"); testDevice3.setManufacturer("Intel"); testDevice3.setType("Processors"); deviceCollection.put(testDevice3.getId(), testDevice3); } }
Next we will design the controller component for receiving and handing requests. Note that in the method getDevices() we have used the annotation ‘RequestMapping’ to designate that it is a http ‘GET’ method and that will get invoked for the path ‘/devices’.
package net.javabeat.articles.rest.dm.model.web; import java.io.StringReader; import java.util.Set; import javax.xml.transform.Source; import javax.xml.transform.stream.StreamSource; import net.javabeat.articles.rest.dm.model.Device; import net.javabeat.articles.rest.dm.model.DeviceCollection; import net.javabeat.articles.rest.dm.service.DeviceServiceImpl; import org.springframework.oxm.jaxb.Jaxb2Marshaller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.portlet.ModelAndView; @Controller public class DeviceController { private DeviceServiceImpl deviceService; private Jaxb2Marshaller marshaller; public DeviceServiceImpl getDeviceService() { return deviceService; } public void setDeviceService(DeviceServiceImpl deviceService) { this.deviceService = deviceService; } public Jaxb2Marshaller getMarshaller() { return marshaller; } public void setMarshaller(Jaxb2Marshaller marshaller) { this.marshaller = marshaller; } @RequestMapping(method=RequestMethod.GET, value="/devices") public ModelAndView getDevices(){ Set devices = deviceService.getAllDevices(); DeviceCollection deviceCollection = new DeviceCollection(); deviceCollection.setAllDevices(devices); return new ModelAndView("devices", "devices", deviceCollection); } @RequestMapping(method=RequestMethod.GET, value="/devices", headers="Accept=application/xml, application/json") public @ResponseBody DeviceCollection getAllDevices() { Set allDevices = deviceService.getAllDevices(); DeviceCollection deviceCollection = new DeviceCollection(); deviceCollection.setAllDevices(allDevices); return deviceCollection; } @RequestMapping(method=RequestMethod.GET, value="/device/{deviceId}") public ModelAndView getADevice(@PathVariable String deviceId) { Device device = deviceService.getDevice(deviceId); return new ModelAndView("devices", "devices", device); } @RequestMapping(method=RequestMethod.GET, value="/device/{deviceId}", headers="Accept=application/xml, application/json") public @ResponseBody Device getDevice(@PathVariable String deviceId) { Device device = deviceService.getDevice(deviceId); return device; } @RequestMapping(method=RequestMethod.POST, value="/device") public ModelAndView addDevice(@RequestBody String body){ Source source = new StreamSource(new StringReader(body)); Device e = (Device) marshaller.unmarshal(source); deviceService.addDevice(e); return new ModelAndView("devices", "devices", e); } }
The method ‘getAllDevices’ will be called if the client includes the accept header containing JSON or the XML format. The return type of this method is annotated with ‘@ResponseBody’, the means that the return object will be serialized and will be included in the http response body. The serialization of the return result to the response body is taken care by Spring’s HTTP message converters. We will look them shortly in this section. Also note that usage of ‘@PathVariable’ annotation in the method ‘getADevice’. This is equivalent to ‘@PathParam’ that we saw in the last section.
<?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" 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-3.0.xsd"> <context:component-scan base-package="net.javabeat.articles.rest.dm.model.web" /> <bean /> <bean id="marshallingConverter"> <constructor-arg ref="marshaller" /> <property name="supportedMediaTypes" value="application/xml"/> </bean> <bean id="jsonConverter"> <property name="supportedMediaTypes" value="application/json" /> </bean> <bean> <property name="messageConverters"> <list> <ref bean="marshallingConverter" /> <ref bean="jsonConverter" /> </list> </property> </bean> <bean id="marshaller"> <property name="classesToBeBound"> <list> <value>net.javabeat.articles.rest.dm.model.Device</value> <value>net.javabeat.articles.rest.dm.model.DeviceCollection</value> </list> </property> </bean> <bean id="deviceController"> <property name="deviceService" ref="deviceService" /> <property name="marshaller" ref="marshaller" /> </bean> </beans>
The above spring context has definition for controllers, http message converters and marshallers. We want to adding marshaling facility to our classes ‘Device’ and ‘DeviceCollection’ so that they can be marshaled the objects can be marshaled to XML stream. For that we have defined the marshaller bean ‘marshaller’. Http message converter objects have to be configured as we read xml/json messages from the response body and then have to be converted to the desired object. We have used the ‘MarshallingHttpMessageConverter’ and ‘MappingJacksonHttpMessageConverter’ for this purpose.
package net.javabeat.articles.rest.dm.client; import java.util.Arrays; import net.javabeat.articles.rest.dm.model.Device; import net.javabeat.articles.rest.dm.model.DeviceCollection; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.http.converter.json.MappingJacksonHttpMessageConverter; import org.springframework.http.converter.xml.MarshallingHttpMessageConverter; import org.springframework.oxm.jaxb.Jaxb2Marshaller; import org.springframework.web.client.RestTemplate; public class DeviceClient { public static void main(String[] args) { RestTemplate restTemplate = getTemplate(); listAllDevicesInXml(restTemplate); listAllDevicesInJson(restTemplate); postDevice(restTemplate); } private static void listAllDevicesInXml(RestTemplate rest) { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_XML); HttpEntity entity = new HttpEntity(headers); ResponseEntity response = rest.exchange( "http://localhost:8080/REST-DeviceManager/dm-service/devices", HttpMethod.GET, entity, DeviceCollection.class); DeviceCollection allDevices = response.getBody(); for(Device aDevice : allDevices.getAllDevices()) { System.out.println(aDevice.getId() + ": " + aDevice.getName()); } } private static void listAllDevicesInJson(RestTemplate rest) { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); HttpEntity entity = new HttpEntity(headers); ResponseEntity response = rest.exchange( "http://localhost:8080/REST-DeviceManager/dm-service/devices", HttpMethod.GET, entity, DeviceCollection.class); DeviceCollection allDevices = response.getBody(); for(Device aDevice : allDevices.getAllDevices()) { System.out.println(aDevice.getId() + ": " + aDevice.getName()); } } public static void postDevice(RestTemplate rest) { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_XML); Device newDeviceObject = new Device(); newDeviceObject.setName("Test-Name"); newDeviceObject.setManufacturer("Test-Manufacturer"); newDeviceObject.setType("Test-Type"); HttpEntity entity = new HttpEntity(newDeviceObject, headers); ResponseEntity response = rest.postForEntity( "http://localhost:8080/REST-DeviceManager/dm-service/device", entity, Device.class); Device device = response.getBody(); System.out.println(device.getId()); } private static RestTemplate getTemplate() { RestTemplate restTemplate = new RestTemplate(); Jaxb2Marshaller marshaller = new Jaxb2Marshaller(); marshaller.setClassesToBeBound(Device.class, DeviceCollection.class); MarshallingHttpMessageConverter marshallingHttpConverter = new MarshallingHttpMessageConverter(marshaller); marshallingHttpConverter.setSupportedMediaTypes(Arrays.asList(MediaType.APPLICATION_XML)); restTemplate.getMessageConverters().add(marshallingHttpConverter); MappingJacksonHttpMessageConverter converter = new MappingJacksonHttpMessageConverter(); converter.setSupportedMediaTypes(Arrays.asList(MediaType.APPLICATION_JSON)); restTemplate.getMessageConverters().add(converter); return restTemplate; } }
Finally we will see the usage of RestTemplate in accessing the device manager application thereby illustrating the usage of other related APIs. Rather than dealing with raw data, we can directly access with the desired type by passing appropriate arguments to the exchange() method. In the method ‘listAllDevicesInXml()’, we have send a GET request, heading accepting the XML format and the desired expected response type. Note the usage of the class HttpHeaders class which is used for encapsulating http headers.
Conclusion
This article started with the basics of RESTful services by making use of Jersey implementation and also provided plenty of code samples. It then illustrated the usage of RestTemplate API which acts as a client side interface for accessing the services. Finally the article concluded with the design of RESTful services in the Web tier.
If you have any questions on the spring REST services, please post it in the comments section. Also search in our website to find lot of other interesting articles related to the spring framework. There are some interesting articles about spring framework, interview questions, spring and hibernate integration,etc. Also refer the spring recommendations for spring books.
also read:
If you would like to receive the future java articles from our website, please subscribe here.