Wednesday 9 February 2011

RESTful webservices with Spring

REST (Representational State Transfer) is a stateless architectural style wherein resources are identified and manipulated. by their URIs and transferred over HTTP. A REST web service sends any parameters, data needed by the server within the HTTP headers and body.
In this article, we will build REST webservices with Spring and a client using the Spring RestTemplate to consume the webservice. I will not be going into the details of these technologies, the aim is to rapidly come up with a basic CRUD web-service.

I will be assuming, that you are familiar with the basics of Spring, DI and IoC, REST, and the basic web-application structure in java.

Without much ado, let's begin!


If you've already built a Spring MVC application, writing a REST web-service should come easily. You can find a tutorial on how to build a simple Spring MVC application here.

I will be using the STS IDE (SpringSource Tool Suite). You can use any eclipse installation, or infact just create a similar package structure yourself. The source of the sample application demonstrated in this article is available for download at the bottom of this page.

STEP 1: Create a Spring MVC template project

Select  File -> New -> Spring Template Project -> Spring MVC Project.
Choose an appropriate project name, and a base package name eg. CMSService, com.test.cmsservice


The IDE will create a basic web-application skeleton for you to work with.

STEP 2: Add maven dependencies
You will find a maven pom.xml at the base of the project. We would need some additional libraries. Add the following dependencies to your pom.xml:
<dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-beans</artifactId>
      <version>${org.springframework-version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context-support</artifactId>
      <version>${org.springframework-version}</version>
      <exclusions>
        <exclusion>
          <groupId>quartz</groupId>
          <artifactId>quartz</artifactId>
        </exclusion>
      </exclusions>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</artifactId>
      <version>${org.springframework-version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-test</artifactId>
      <version>${org.springframework-version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-orm</artifactId>
      <version>${org.springframework-version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-oxm</artifactId>
      <version>${org.springframework-version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-web</artifactId>
      <version>${org.springframework-version}</version>
    </dependency>
    <dependency>
      <groupId>cglib</groupId>
      <artifactId>cglib-nodep</artifactId>
      <version>2.1_3</version>
    </dependency>
    <dependency>
      <groupId>javax.xml.bind</groupId>
      <artifactId>jaxb-api</artifactId>
      <version>2.0</version>
    </dependency>
If you are not using maven, you will have to download and add the necessary jars to your classpath manually. 

STEP 3: Create a domain class

Create a new package com.test.cmsservice.domain and a simple Customer domain class that has a couple of properties and corresponding getters and setters.
package com.test.cmsservice.domain;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement(name="customer")
public class Customer {

    Long id;
    String firstName;
    String lastName;
    String emailAddress;
    String phoneNumber;
   
    public Customer(){}
   
    public Customer(Long id, String firstName, String lastName, String emailAddress, String   phoneNumber){
        this.id = id;
        this.firstName = firstName;
        this.lastName = lastName;
        this.emailAddress = emailAddress;
        this.phoneNumber = phoneNumber;
    }
   
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getFirstName() {
        return firstName;
    }
    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }
    public String getLastName() {
        return lastName;
    }
    public void setLastName(String lastName) {
        this.lastName = lastName;
    }
    public String getEmailAddress() {
        return emailAddress;
    }
    public void setEmailAddress(String emailAddress) {
        this.emailAddress = emailAddress;
    }
    public String getPhoneNumber() {
        return phoneNumber;
    }
    public void setPhoneNumber(String phoneNumber) {
        this.phoneNumber = phoneNumber;
    }
}
Note the @XmlRootElement annotation at the class level. When a class is so annotated, its value is represented as an XML root element in an XML document. Use of these annotations enables these classes to be mapped to schema with minor changes to the code.

We will also create a CustomerList class that acts as a wrapper class for sending across a list of all the customers in the system.
package com.test.cmsservice.domain;

import java.io.Serializable;
import java.util.List;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement(name="customers")
public class CustomerList implements Serializable{
   
    private List<Customer> customers;
   
    public CustomerList(){}
    public CustomerList(List<Customer> customers){
        this.customers = customers;
    }
   
    @XmlElement(name="customer")
    public List<Customer> getCustomers() {
        return customers;
    }
    public void setCustomers(List<Customer> customers) {
        this.customers = customers;
    }

}

STEP 4: Create a service class

In a working application, you'd store the customers in a database, but to focus on REST, i will be storing the customers in a static list in the service class (Find a tutorial on Spring Hibernate integration here)
package com.test.cmsservice.service;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import com.test.cmsservice.domain.Customer;

public class CustomerService {

    static HashMap<Long, Customer> customers = new HashMap<Long, Customer>();
   
    static {
        customers.put(1L, new Customer(1L, "Stephen", "Smith", "Stephen.Smith@xyz.abc", "34545634"));
        customers.put(2L, new Customer(2L, "Mary", "Johnson", "Mary.Johnson@abc.xyz", "32445433"));
    }
   
    public Customer getCustomer(Long customerId){
        return customers.get(customerId);
    }
    public void saveCustomer(Customer customer){
        customers.put(customer.getId(), customer);
    }
    public List<Customer> getCustomers(){
        return new ArrayList(customers.values());
    }
    public void deleteCustomer(Long customerId){
        customers.remove(customerId);
    }
    public void updateCustomer(Customer customer){
        customers.put(customer.getId(), customer);
    }
}
Pretty basic.

STEP 5: Create a controller class
This is where the interesting part lies. Create a package com.test.cmsservice.web and a class CustomerController as follows:
package com.test.cmsservice.web;

/*import statements ...*/

@Controller
@RequestMapping("customer")
public class CustomerController {
   
    @Autowired
    CustomerService customerService;
   
    @RequestMapping(method=RequestMethod.GET)
    public ModelAndView getAllCustomers(ModelMap model)
    {
        List<Customer> customers = customerService.getCustomers();
        CustomerList customerList = new CustomerList(customers);
        return new ModelAndView("customerView", "customers", customerList);
    }
   
    @RequestMapping(value="/{id}", method = RequestMethod.GET)
    public ModelAndView getCustomer(@PathVariable Long id){
        Customer customer = customerService.getCustomer(id);
        return new ModelAndView("customerView", "customer", customer);

    }
   
    @RequestMapping(method = RequestMethod.POST)
    public View addCustomer(@RequestBody Customer customer)
    {
        customerService.saveCustomer(customer);
        return new RedirectView("/customerView/"+ customer.getId());
    }
}
 The @Controller annotation enables detection when the spring container scans the application at load time and informs the container that the class will behave as a Controller.

The @RequestMapping annotation marks a function that can handle HTTP requests. A request that matches the URL pattern and HTTP method provided in the 'value' and 'method' attributes of the @RequestMapping annotation would be delegated to the corresponding function. In this case, we have used the @RequestMapping annotation at the class level as well. The attributes that are common to all the methods can be specified at the class level.

The @PathVariable annotation provides a  means to access the parameters passed in the URI. For eg, an HTTP request to fetch the details of a customer with id '1' would look like this:
  http://localhost:CMSService/customer/1
The {id} part of the URI is made available to the method using the @PathVariable annotation.

The CustomerService instance is autowired and will be passed on to the Controller class when the spring context is loaded.
Also notice, that the controller returns a ModelAndView object with a view name 'customerView'. This view is configured in the spring context xml, which we will look at shortly.

[Note: I have implemented only the GET an POST methods, you can also try deleting or updating the customers using the RequestMethod.DELETE and RequestMethod.PUT method attributes in the @RequestMapping annotation on methods.]

STEP 6: Set up the configuration files.
If you are using the STS IDE, you would not need to set up your web.xml file, since this is already done for you. This is how your web.xml should look.
<!-- Creates the Spring Container shared by all Servlets and Filters -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <!-- Processes application requests -->
    <servlet>
        <servlet-name>appServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
       
    <servlet-mapping>
        <servlet-name>appServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
 This basically sends all the requests that are recieved to Spring's DispatcherServlet (which is the Front controller) We are also specifying that the base spring context file is 'servlet-context.xml' file. Let's look at the servlet-context.xml next.
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/mvc"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:beans="http://www.springframework.org/schema/beans"
    xsi:schemaLocation="
        http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

    <!-- DispatcherServlet Context: defines this servlet's request-processing
        infrastructure -->

    <!-- Enables the Spring MVC @Controller programming model -->
    <annotation-driven />

    <!-- Handles HTTP GET requests for /resources/** by efficiently serving
        up static resources in the ${webappRoot}/resources directory -->
    <resources mapping="/resources/**" location="/resources/" />

    <beans:bean id="jaxbMarshaller"
        class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
        <beans:property name="classesToBeBound">
            <beans:list>
                <beans:value>com.test.cmsservice.domain.Customer</beans:value>
                <beans:value>com.test.cmsservice.domain.CustomerList</beans:value>
            </beans:list>
        </beans:property>
    </beans:bean>

    <beans:bean id="customerView"
        class="org.springframework.web.servlet.view.xml.MarshallingView">
        <beans:constructor-arg ref="jaxbMarshaller" />
    </beans:bean>

    <beans:bean
        class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
        <beans:property name="mediaTypes">
            <beans:map>
                <beans:entry key="xml" value="application/xml" />
                <beans:entry key="html" value="text/html" />
            </beans:map>
        </beans:property>
        <beans:property name="viewResolvers">
            <beans:list>
                <beans:bean
                    class="org.springframework.web.servlet.view.BeanNameViewResolver" />
                <beans:bean id="viewResolver"
                    class="org.springframework.web.servlet.view.UrlBasedViewResolver">
                    <beans:property name="viewClass"
                        value="org.springframework.web.servlet.view.JstlView" />
                    <beans:property name="prefix" value="/WEB-INF/views/" />
                    <beans:property name="suffix" value=".jsp" />
                </beans:bean>
            </beans:list>
        </beans:property>
    </beans:bean>

    <!-- Imports user-defined @Controller beans that process client requests -->
    <beans:import resource="controllers.xml" />
</beans:beans>
The <annotation-driven> tag informs the container that it needs to look for annotated classes.

Next we define a jaxbMarshaller bean, to marshall and unmarshall the domain classes. A list of domain classes to be handled is passed next. We also specify a couple of viewResolvers for each mediaType.

Next we define a MarshallingView bean named 'customerView'. This bean takes the jaxbMarshaller bean to transform the object into an xml document. This is the view name being used to construct the ModelAndView objects returned by the controller methods.


Next we define a ContentNegotiationViewResolver. What is content negotiation? It is the concept where the server returns a specific representation of the objects depending upon the content-type it recieves in the request. The ContentNegotiationViewResolver can switch the views depending on the request type (specified by the Accept request header) Several types are supported such as HTML, XML, JSON, ATOM, PDF etc. In this case we have defined 2 supported 'mediaTypes' : xml and html.We also define viewResolvers for each mediaType.

[Note: If you want to render a jsp file instead of the xml format, create a customer.jsp file in /WEB-INF/views, and simply change the view name  in the controller class from :

return new ModelAndView("customerView", "customer", customer);
                 to
return new ModelAndView("customer", "customer", customer);

The UrlBasedViewResolver viewResolver will then find the file customer.jsp in the /WEB-INF/views folder

and render the object. ]

Next, we import the controllers.xml file which specifies the base package for component scanning (to look for annotated classed)
<context:component-scan base-package="com.test.cmsservice" />

STEP 7: Build and Run!
You are now ready to test your Spring REST webservice. Right click the project and click 'Run on Server'.
Browse to http://localhost:8080/CMSService/customer, you should be able to see something like this:
Also try this: http://localhost:8080/CMSService/customer/2


If you are not using the IDE, you can use curl or write a small Spring REST based client which i will explain next.

STEP 8: Write a Spring REST web-service client
Writing a web-service client using Spring REST is pretty easy. The most important class is the RestTemplate class which is meant to be used the way we use JdbcTemplate, JmsTemplate etc.

For this demo, i am adding a client-context.xml to the same project inside the /WEB_INF/spring folder.
client-context.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"
    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.xml.MarshallingHttpMessageConverter">
                    <property name="marshaller" ref="jaxbMarshaller" />
                    <property name="unmarshaller" ref="jaxbMarshaller" />

                </bean>
                <bean class="org.springframework.http.converter.FormHttpMessageConverter" />
            </list>
        </property>
    </bean>

    <bean id="jaxbMarshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
        <property name="classesToBeBound">
            <list>
                <value>com.test.cmsservice.domain.Customer</value>
                <value>com.test.cmsservice.domain.CustomerList</value>
            </list>
        </property>
    </bean>
</beans>
The restTemplate bean is passed the jaxbMarshaller bean which it will use to marshall and unmarshall resources that it sends or recieves. The jaxbMarshaller is passed a list of the domain class.

The RestTemplate has a couple of useful methods that you can use to POST, GET, DELETE and PUT data.
We will use a few of them in our client code:
package com.test.cmsservice.client;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.web.client.RestTemplate;
import com.test.cmsservice.domain.Customer;
import com.test.cmsservice.domain.CustomerList;

public class RestClient {

    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("client-context.xml");
        RestTemplate restTemplate = (RestTemplate)ctx.getBean("restTemplate");

        addCustomer(restTemplate);
        getAllCustomers(restTemplate);
    }
   
    public static void addCustomer(RestTemplate restTemplate){
        System.out.println("addCustomer called");
        Customer customer = new Customer(3L, "Bill","Gates", "bill@microsoft.com","234");
        restTemplate.postForLocation(
        "http://localhost:8080/CMSService/customer", customer);
       
    }
   
    public static void getAllCustomers(RestTemplate restTemplate){
        CustomerList customers = (CustomerList) restTemplate.getForObject(
                "http://localhost:8080/CMSService/customer", CustomerList.class);
        for(Customer customer:  customers.getCustomers()){
            System.out.println("Customer: "+ customer.getId());
            System.out.println("Name: "+ customer.getFirstName() + " " + customer.getLastName());
            System.out.println("Email: "+ customer.getEmailAddress());
        }
    }
}
The client code is pretty straightforward. We are first adding a customer using the postForLocation method which takes the web-service url and the object to be POSTed.
We then fetch the list of all the customers by using the method getForObject which returns a CustomerList object.
Try your hand at the put, delete, exchange methods of the RestTemplate class for deleting and updating the customers using the webservice.

Download the source code of the demo

Hope you find this article helpful.
Happy coding!

15 comments:

  1. Hello,

    I did try the project from the source code and get the following:
    http://localhost:8080/CMSService/customer

    HTTP ERROR 404

    Problem accessing /CMSService/WEB-INF/views/customerView.jsp. Reason:

    Not Found

    ReplyDelete
  2. Hi there, could you please check if you have defined the bean :

    < beans:bean id="customerView"
    class="org.springframework.web.servlet.view.xml.MarshallingView" >


    in your servlet-context.xml ?

    If you don't specify this bean in the context file, it will try to find a file named 'customerView.jsp' in the WEB-INF folder which is not what we'd want. We want to pass the object to the JaxbMarshaller bean. Please revisit step 5 and 6

    ReplyDelete
  3. Thank you for the example. I think I am a bit confused on how the unmarshalling process works. I am trying to use this with Bing rest location service. My response is


    -
    Copyright © 2011 Microsoft and its suppliers. All rights reserved. This API cannot be accessed and the content and any results may not be used, reproduced or transmitted in any manner without express written permission from Microsoft Corporation.
    http://dev.virtualearth.net/Branding/logo_powered_by.png
    200
    OK
    ValidCredentials
    4f6cc8cd6e9443d48c70d8f26787a427|BAYM001220|02.00.82.1400|BY2MSNVM001068
    -
    -
    1
    -
    -
    1 Microsoft Way, Redmond, WA 98052-8300
    -
    47.639747
    -122.129731

    -
    47.635884282429323
    -122.13737419709076
    47.643609717570676
    -122.12208780290925

    Address
    -
    1 Microsoft Way
    WA
    King Co.
    United States
    1 Microsoft Way, Redmond, WA 98052-8300
    Redmond
    98052-8300

    High






    I created a class called Response and added the @XmlRootElement notation.

    I also set up my test class the same way as the example:

    ApplicationContext ctx = new ClassPathXmlApplicationContext("client-context.xml");
    RestTemplate restTemplate = (RestTemplate) ctx.getBean("restTemplate");

    Response blah = restTemplate.getForObject(
    "http://dev.virtualearth.net/REST/v1/Locations/US/WA/98052/Redmond/1 Microsoft Way?o=xml&key="+bingKey, Response.class);

    System.out.println(blah);

    I keep getting the following error:

    Exception in thread "main" org.springframework.http.converter.HttpMessageNotReadableException: Could not read [class com.symmetry.calculator.model.bing.Response]; nested exception is org.springframework.oxm.UnmarshallingFailureException: JAXB unmarshalling exception; nested exception is javax.xml.bind.UnmarshalException: unexpected element (uri:"http://schemas.microsoft.com/search/local/ws/rest/v1", local:"Response"). Expected elements are <{}response>

    Can you offer any suggestions?

    Thanks

    ReplyDelete
  4. Hi blong824,

    Looks like there's something missing in your 'Response' class. Do you think there might be any missing fields or incorrect mappings? You may want to add annotate all the fields to map them correctly to the elements in the xsd.

    Also, how have you generated your Response class.. there could be a gap between the xml response from the WS and your Response class.

    You may find the following useful
    1. http://publib.boulder.ibm.com/infocenter/wasinfo/v7r0/index.jsp?topic=/com.ibm.websphere.express.doc/info/exp/ae/twbs_jaxbschema2java.html

    for generating java classes using the response xml

    ReplyDelete
  5. Thanks for this example. I was not able to get it to run until I added an @Service annotation to CustomerService class. Without the @Service annontation, I recieved the following error:
    Error creating bean with name 'customerController': Injection of autowired dependencies failed...

    ReplyDelete
  6. Thanks for the tutorial!

    How would you use curl for a POST? Can you provide a code example?

    Thanks.

    ReplyDelete
  7. This comment has been removed by the author.

    ReplyDelete
  8. About NYC post! and your answer:

    Something dont work fine! i dont know why but when you call http://localhost:8080/CMSService/customer the program try to find customerView.jsp

    If you delete the UrLBasedVIewResolver beans the program dont try to find customerView.jsp and it works showing the XML

    I just run your code and you have:

    < beans:bean id="customerView"
    class="org.springframework.web.servlet.view.xml.MarshallingView" >

    I think it's correct but i dont know why it's doesn't work :S

    Sorry about my english

    Thanks

    ReplyDelete
    Replies
    1. This comment has been removed by the author.

      Delete
  9. Thank again for another good tutorial! In the downloaded code version I had to remove some lines from the servlet-context.xml file to get it to display the xml. I had to remove the entry with the jsp view resolver.

    Thanks!

    ReplyDelete
  10. Simple and awesome tutorial. I'm a newbie and it helped me out in understanding rest implementation in spring. Thanks!

    ReplyDelete
  11. This comment has been removed by the author.

    ReplyDelete
  12. Thanks for the tutorial. I am not the the o/p in xml format rather like a string as below. Would appreciate if you could tell what to change to get the xml o/p.

    Stephen.Smith@xyz.abcStephen1Smith34545634Mary.Johnson@abc.xyzMary2Johnson32445433

    Thanks
    Vijay

    ReplyDelete
    Replies
    1. This comment has been removed by the author.

      Delete
    2. Also when i use

      beans:import resource="controllers.xml"

      in the context file i am getting below error.

      Failed to resolve current resource location: Cannot create relative resource 'controller.xml' for file [/
      home/vkveera/Documents/workspace-Learning/RestCustService/src/main/webapp/WEB-INF/spring/
      appServlet/servlet-context.xml]

      I commented that and added the below
      context:component-scan base-package="com.test.cmsservice"

      but want to know is this the reason for printing as string instead of xml format ?

      Delete