Friday 16 October 2015

RESTing In Spring

What is REST?

REpresentational State Transfer or REST, in short, is a kind of web service that deals with transfer of state of resources from one application to another. REST resources can be represented in various forms like JSON, XML, HTML etc. This is one of the few reasons REST has gained popularity in recent years over its traditional sibling, SOAP. While SOAP is heavy weight, action based, REST is lightweight, resource based.

Spring, being one of the popular frameworks, supports REST i.e., we can develop RESTful web services using Spring. Here I will show you following two things:

1. How to create a REST Service using Spring?
2. How to write a REST client using Spring?

Example

Suppose Credit Bureau of India maintains a repository of credit information of different customers depending on their income, spending and saving patterns. It exposes this info to various retails and banks as a Web Service which can be used by them to promote their products, provide loans etc.

Now we will create the web service exposed by Credit Bureau of India as a REST service and write a REST client that will be used by retails and banks to avail this service. We will use Spring in conjunction with JPA to create this REST service.

Softwares / Tools

1. Spring 4.2.1.RELEASE
2. Hibernate 5.0.1.Final
3. Jackson 2.2.3
4. MySQL 5.5
5. JDK 8
6. Eclipse Luna 4.4.1
7. Apache Tomcat v8.0
8. Maven 3.0

Steps

  • Create a Maven project in eclipse by selecting File -> New -> Maven Project. Name the project as 'RestApp'. The project structure of the RestApp will look like the figure given below.
Maven Project Structure of RestApp
  •  Following is the pom.xml file.
<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/xsd/maven-4.0.0.xsd">
      <modelVersion>4.0.0</modelVersion>
      <groupId>com</groupId>
      <artifactId>RestApp</artifactId>
      <version>0.0.1-SNAPSHOT</version>
      <packaging>war</packaging>
     
      <properties>
          <spring.framework.version>4.2.1.RELEASE</spring.framework.version>
          <hibernate.version>5.0.1.Final</hibernate.version>
          <jackson.version>2.2.3</jackson.version>
      </properties>
 
      <dependencies>
          <dependency>
              <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.framework.version}</version>
          </dependency>
          <dependency>
              <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${spring.framework.version}</version>
          </dependency>
          <dependency>
              <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>${spring.framework.version}</version>
          </dependency>
        <dependency>
              <groupId>org.springframework</groupId>
                <artifactId>spring-orm</artifactId>
            <version>${spring.framework.version}</version>
        </dependency>
        <dependency>
          <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
            <version>${hibernate.version}</version>
        </dependency>
        <dependency>
          <groupId>org.hibernate</groupId>
            <artifactId>hibernate-core</artifactId>
            <version>${hibernate.version}</version>
        </dependency>
        <dependency>
          <groupId>com.fasterxml.jackson.core</groupId>
          <artifactId>jackson-databind</artifactId>
          <version>${jackson.version}</version>
        </dependency>
        <dependency>
          <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.20</version>
        </dependency>
      </dependencies>
      <build>
          <finalName>RestApp</finalName>
      </build>
</project>


  • First let us create the resource which is Customer here. 
package com.programnplay.entity;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(schema = "mydb")
public class Customer{
   
    @Id
    @Column(name = "Id")
    private Integer id; 
    @Column(name = "FirstName")
    private String firstName;
    @Column(name = "LastName")
    private String lastName;
    @Column(name = "City")
    private String city;
    @Column(name = "Score")
    private Integer score;
    @Column(name = "Rating")
    private String rating;
   
    public Integer getId(){
        return id;
    }
    public void setId(Integer 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 getCity() {
        return city;
    }
    public void setCity(String city) {
        this.city = city;
    }
    public Integer getScore() {
        return score;
    }
    public void setScore(Integer score) {
        this.score = score;
    }
    public String getRating() {
        return rating;
    }
    public void setRating(String rating) {
        this.rating = rating;
    }
   
    @Override
    public String toString() {
        return "Customer [id=" + id + ", firstName=" + firstName
                + ", lastName=" + lastName + ", city=" + city
                + ", score=" + score + ", rating=" + rating + "]";
    }
}


It is a simple POJO class containing various attributes like firstName, lastName, credit score, credit rating etc of  a customer. We have used JPA annotations to map this entity to table Customer and its various columns in MySQL database. The structure of Customer table is as follows.

Field           Type                      Null    Key 
---------        -----------               ------  ------
Id                int(3)                     NO      PRI            
FirstName  varchar(15)            YES     
LastName  varchar(15)            YES                   
City            varchar(20)            YES                    
Score          int(4)                     YES                    
Rating        varchar(12)            YES        

  • Like any JPA programming, we have to use persistence.xml file to connect our application to database. The contents of persistence.xml file which is placed under /resources/META-INF/ folder is given below.

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
    <persistence-unit name="RestPU" transaction-type="RESOURCE_LOCAL">
        <provider>org.hibernate.ejb.HibernatePersistence</provider>
        <class>com.
programnplay.entity.Customer</class>
        <properties>
            <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
             <property name="javax.persistence.jdbc.user" value="abc"/>
             <property name="javax.persistence.jdbc.password" value="abc"/>
             <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/mydb"/>
             <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect"/>
      </properties>
    </persistence-unit>
</persistence>


We have used Hibernate as persistence provider, MySQL as database and MySQLDialect as dialect.
  • Spring implements REST using Web MVC architecture. So we will now create a controller class, CreditController.java that does different CRUD (C-Create, R-Read, U-Update, D-Delete) operations on the resource, Customer.
package com.programnplay.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import com.
programnplay.dao.CustomerDAO;
import com.
programnplay.entity.Customer;

@RestController
@RequestMapping("/CreditService")
public class CreditController {
   
    @Autowired
    @Qualifier("customerDAOImpl")
    private CustomerDAO custDAO;
   
   //Create Customer

   @RequestMapping(method=RequestMethod.POST)
    public Customer createCustomer(@RequestBody Customer customer){
        System.out.println("Start of createCustomer() method in CreditController");
        custDAO.createCustomer(customer);
        return customer;
    }
   
   //Read Customer

   @RequestMapping(value="/{id}", method=RequestMethod.GET)
    public Customer getCustomer(@PathVariable Integer id){
        System.out.println("Start of getCustomer() method in CreditController");
        Customer customer = custDAO.getCustomer(id);
        return customer;
    }
   
    //Update Customer

    @RequestMapping(method=RequestMethod.PUT)
    @ResponseStatus(value = HttpStatus.OK)
    public void updateCustomer(@RequestBody Customer customer){
        System.out.println("Start of updateCustomer() method in CreditController");
        custDAO.updateCustomer(customer);
    }
   
    //Delete Customer

    @RequestMapping(value="/{id}", method=RequestMethod.DELETE)
    @ResponseStatus(value = HttpStatus.OK)
    public void deleteCustomer(@PathVariable Integer id){
        System.out.println("Start of deleteCustomer() method in CreditController");
        custDAO.deleteCustomer(id);
    }
}


In order to distinguish the controller that handles REST service from other controllers, Spring 4.0 provides @RestController annotation. So we have used this annotation for our class CreditController.java. First method createCustomer() returns the Customer it creates while the second method getCustomer() is used to retrieve a Customer basing on the customer id passed in the request. But the last two methods, updateCustomer() and deleteCustomer() don't return anything. Mark @ResponseStatus applied to these two methods. By applying @ResponseStatus as HttpStatus.OK, we have told Spring explicitly not to expect any return items from these methods. So it will not use any HttpMessageConverter to convert Java objects to raw form. To serialize and de-serialize Java objects to response body and request body, we have used MappingJackson2HttpMessageConverter here. For this, we just need to place Jackson 2's jar in our application classpath.

  •  The controller CreditController.java is dependent on dao, CustomerDAO. The code for its implementation class CustomerDAOImpl is given below.
package com.programnplay.dao;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import com.
programnplay.entity.Customer;

@Repository
@Transactional
public class CustomerDAOImpl implements CustomerDAO{
   
    @PersistenceContext
    private EntityManager em;
   
    public void createCustomer(Customer customer){
        em.persist(customer);
    }
   
    public Customer getCustomer(int id) {
        Customer customer = em.find(Customer.class, id);
        return customer;
    }

    public void updateCustomer(Customer customer) {
        em.merge(customer);
    }

    public void deleteCustomer(int id) {
        Customer customer = getCustomer(id);
        em.remove(customer);
    }
}

    This is a simple DAO containing methods for four CRUD operations. All these methods are transactional in nature. Each of these methods invokes corresponding EntityManager method to create, replace, update and delete the resource.
    •  As we will deploy this REST service as WAR, let us write the web.xml file.
    <?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>RestApp</display-name>
          <servlet>
            <servlet-name>REST</servlet-name>
            <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
            <load-on-startup>1</load-on-startup>
        </servlet>
        <servlet-mapping>
            <servlet-name>REST</servlet-name>
            <url-pattern>/</url-pattern>
        </servlet-mapping>

        <welcome-file-list>
           <welcome-file>index.jsp</welcome-file>
        </welcome-file-list>
      </web-app> 

      • Spring configuration file REST-servlet.xml contains the following beans.
      <?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:mvc="http://www.springframework.org/schema/mvc"
             xmlns:tx="http://www.springframework.org/schema/tx"
             xmlns:context="http://www.springframework.org/schema/context"
             xsi:schemaLocation="http://www.springframework.org/schema/beans
                                     http://www.springframework.org/schema/beans/spring-beans.xsd
                                     http://www.springframework.org/schema/mvc
                                     http://www.springframework.org/schema/mvc/spring-mvc.xsd
                                     http://www.springframework.org/schema/tx
                                     http://www.springframework.org/schema/tx/spring-tx.xsd
                                     http://www.springframework.org/schema/context
                                     http://www.springframework.org/schema/context/spring-context.xsd">
      <mvc:annotation-driven/>
      <tx:annotation-driven/>
      <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalEntityManagerFactoryBean">        
          <property name="persistenceUnitName" value="RestPU"/>  
      </bean>
      <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
             <property name="entityManagerFactory" ref="entityManagerFactory"/>
      </bean>
      <context:component-scan base-package="com.
      programnplay.controller"/>
      <context:component-scan base-package="com.
      programnplay.dao"/>
      </beans> 
                               

      • Now go to the root of the RestApp project in the command prompt and run 'mvn clean package' command to create RestApp.war. This brings an end to the development of REST service using Spring.
      To test above REST service, we can use various tools like RestClient available in the market or create a stand-alone application and run it. Spring provides RestTemplate to let programmers write their own stand-alone application to invoke REST service. So let us now create a Rest client using Spring's RestTemplate.
      • Create a 'Credit' directory under C:\ drive. Under 'Credit' directory, let us create com>programnplay>client folder structure. Here let us write the client main program, RestClient.java.
      package com.programnplay.client;

      import org.springframework.web.client.RestTemplate;
      import org.springframework.core.NestedRuntimeException;

      import com.
      programnplay.entity.Customer;

      public class RestClient {

          RestTemplate restTemplate;
         
          public RestClient(){
              restTemplate = new RestTemplate();
          }
         
          public static void main(String[] args) {
              RestClient client = new RestClient();
              client.createCustomer(101,"Rajiba", "Dash", "Bengaluru", 600, "Good");
              Customer customer = client.getCustomer(101);
              customer.setScore(900);
              customer.setRating("Very Good");
              client.updateCustomer(customer);
              customer = client.getCustomer(101);
              client.deleteCustomer(101);
          }
         
          public void createCustomer(int id, String firstName, String lastName, String city, int score, String rating){
              Customer customer = new Customer();
              customer.setId(id);
              customer.setFirstName(firstName);
              customer.setLastName(lastName);
              customer.setCity(city);
              customer.setScore(score);
              customer.setRating(rating);
              restTemplate.postForObject("http://localhost:8080/RestApp/CreditService", customer, Customer.class);
              System.out.println("Customer with id "+id+" was stored successfully");
          }
         
          public Customer getCustomer(int id){
              Customer customer = restTemplate.getForObject("http://localhost:8080/RestApp/CreditService/"+id, Customer.class);
              System.out.println("Customer with id "+id+" was retrieved successfully");
              System.out.println(customer);
              return customer;
          }
         
          public void updateCustomer(Customer customer){
              restTemplate.put("http://localhost:8080/RestApp/CreditService", customer);
              System.out.println("Customer with id "+customer.getId()+" was updated successfully");
          }
         
          public void deleteCustomer(int id){
              restTemplate.delete("http://localhost:8080/RestApp/CreditService/"+id);
              System.out.println("Customer with id "+id+" was deleted successfully");
          }
      }


      In this client, we have declared an instance of RestTemplate. RestTemplate provides methods like getForObject(), postForObject(), put(), delete() etc. to invoke various GET, POST, PUT, DELETE requests respectively. If you observe carefully, we are passing the resource, Customer while invoking each of these requests. This proves that REST is resource based.

      • To compile RestClient.java, we have to import Customer class that we wrote in the beginning to the same directory i.e., 'Credit' and also following jars.
                      commons-logging-1.2.jar
                      hibernate-jpa-2.0-api-1.0.0.Final.jar
                      jackson-annotations-2.2.3.jar
                      jackson-core-2.2.3.jar
                      jackson-databind-2.2.3.jar
                      spring-beans-4.2.1.RELEASE.jar
                      spring-core-4.2.1.RELEASE.jar
                      spring-web-4.2.1.RELEASE.jar
      • Now compile Customer class from the command prompt with the help of command 'javac -cp .;./* com/programnplay/entity/Customer.java'. Next, compile RestClient in the same way by applying command 'javac -cp .;./* com/programnplay/client/RestClient.java'. Refer the Client Log figure given below.
      • Since we have client ready to invoke REST service, let us deploy the RestApp.war that we created earlier in local Tomcat server.
      •  As the server is up and running, let us run the RestClient from the command prompt by applying the below command.
                      java -cp .;./* com.programnplay.client.RestClient

      Within few minutes, we will be able to see the output in client console as shown in below figure.


      Console Output while running REST client
       Let us check server logs in Tomcat.

      Server Log while running REST client
      Both the logs show that we have created our REST service and invoked it successfully through our RestClient.


      Those developers who have worked in Spring 3.0 like me might be thinking what happened to @ResponseBody. My answer is @RestContoller, apart from making a class as Spring MVC controller also does what @ResponseBody does in Spring 3.0 i.e., it converts the response object into raw form. Thus @RestController combines the power of two annotations, @Controller and @ResponseBody in it.

      Bye, see you in my next blog. Wish you a Happy Dussehara and sparkling Diwali.

        No comments:

        Post a Comment