Search This Blog

Monday 27 May 2013

Rest Example with Spring.

Over the last one year I have worked on three projects involving REST based services. I found REST pretty cool as a concept - also the fact that unlike SOAP I did not have to learn any new terminologies. instead I needed to model my existing understanding of HTTP to build services.
Spring has provided a very cool technique to build REST web services. I have used it in all my three projects and have been very impressed with the same. I decided to build a simple REST application using Spring.
My resources here is a simple User entity:
public class User {
 public static final int INVALID_ID = -1;
 private int id = INVALID_ID;
 private String name;
 private int age;
        //setter getters
}
Next Step is to build a REST API to perform CRUD operations on this resource.
REST APIS represent the resource using a URL. So my user resource will be mapped to a URL of the form http://<server_name>/<application_name>/user
In a simple web application we would represent actions associated with the resource using URLs. So for user, we would have URLs of the form http://<server_name>/<application_name>/getuser.do or http://<server_name>/<application_name>/user.do?action=get. Similar URLs would be created for addUser, deleteUser etc.
In REST the operations on the resource are mapped to the HTTP verbs. So a GET will Retrieve, a PUT will Update, a DELETE will... well delete.
So what about Create ?
HTTP has no CREATE action. So generally POST method is used to represent the create operation.
The first Step would be to define the controller:
@Controller
@RequestMapping(value = "/user")
public class RestServiceController {
//more code
The Controller annotation we already saw. This is a simple Spring bean capable of processing HTTP operations. Next is the API.
@Controller
@RequestMapping(value = "/user")
public class RestServiceController {

   public List<User> getUsers() {}

   public User getUser(final int id) {}

   public User addUser(final User user) {}

   public User updateUser(final int id, final User user) {}

   public void deleteUser(final int id) {}

   public User findUserByName(final String name) {}
}
I need to add code in the methods and the appropriate annotations over the methods.Spring's web framework requires the dispatcher Servlet. So
<servlet>
    <servlet-name>dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>dispatcher</servlet-name>
    <url-pattern>/api/*</url-pattern>
  </servlet-mapping>
All URLs beginning with /api will now be handled by Spring's FrontController.

I decided to start with the retrieve method:
private final  Map<Integer, User> allUsers = new HashMap<Integer, User>();
private int keyIndex = 0; 
 
@RequestMapping(method = RequestMethod.GET)
public @ResponseBody
List<User> getUsers() {
 logger.info(" get all Users");
 Collection<User> users = allUsers.values();
 return new ArrayList<User>(users);
}

The method is pretty straight. It will return a list of all users. (In reality the users are all managed in a map, from where the method simply retrieves them.)
The method is very much like our controller methods. The interesting points are in the signature:
  • The method does not return a ModelView object. It does not even return a String that could be used by a ViewResolver.
  • There is a new annotation introduced here is ResponseBody.
As per the code documentation:
Annotation which indicates that a method return value 
should be bound to the web response body.
The annotation basically tells Spring to write the return value of the method to HttpResponse.
So how does Spring write a List of User instances to the response stream ?
Would it call toString method of List class ? And what sense would this make to the client?
As per Spring documentation:
The conversion of the request body to the method argument is done using a HttpMessageConverter. 
HttpMessageConverter is responsible for converting for converting from the HTTP request message
to an object and converting from an object to the HTTP response body.
Spring provides a variety of message converters for the task. I configured a JSON based message converter.
<bean id="jacksonMessageConverter"
      class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter" />

   <bean
      class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
      <property name="messageConverters">
         <list>
            <ref bean="jacksonMessageConverter" />
         </list>
      </property>
   </bean>
The configuration includes a definition of AnnotationMethodHandlerAdapter. As per the docs:
Implementation of the HandlerAdapter interface that maps handler methods 
based on HTTP paths, HTTP methods and request parameters expressed 
through the RequestMapping annotation. 
The class's functionality has been extended in Spring 3.0 to cover these RequestBody and ResponseBody annotations. The class is basically needed when using annotation based handlers. But we don't really define this bean. We simply specify:
<mvc:annotation-driven />
It covers the above bean with default settings. Here we have included a Jackson based MessageConverter to obtain the JSON response.
I added a screenshot of the test:
The logs indicate the usage of the MessageConverter:
2013-05-05 16:24:32 DEBUG ServletInvocableHandlerMethod:123 - Invoking [getUsers
] method with arguments []
2013-05-05 16:24:32 INFO  RestServiceController:54 -  get all Users
2013-05-05 16:24:32 DEBUG ServletInvocableHandlerMethod:129 - Method [getUsers] 
returned [[test.controller.User@585f2a, test.controller.User@1cfe174]]
2013-05-05 16:24:32 DEBUG RequestResponseBodyMethodProcessor:140 - Written [[tes
t.controller.User@585f2a, test.controller.User@1cfe174]] as "application/json;ch
arset=UTF-8" using [org.springframework.http.converter.json.MappingJacksonHttpMe
ssageConverter@be6858]
In the next post I shall look at the implementations for the remaining methods.

1 comment: