Restful code example using Spring MVC

In my previous blog, I talked about Restful webservices design. In this blog, I will provide sample code for the same using Spring MVC framework.

Spring MVC 3.x supports annotation based controllers, which are Restful in nature. 

Let us create a sample Spring web application for performing CRUD on User entity.


Creating project:

Create a Maven project with a archetype 'maven-archetype-webapp'.

pom.xml

<!-- version property -->
<properties>
    <spring.version>3.1.2.RELEASE</spring.version>
</properties>
<!-- servlet dependency for compilation -->
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>servlet-api</artifactId>
    <version>2.5</version>
    <type>jar</type> 
    <scope>compile</scope>
</dependency>

<!-- Spring MVC dependencies -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>${spring.version}</version>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
    <version>${spring.version}</version>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>${spring.version}</version>
</dependency>

<!-- Jackson dependency for JSON mapping-->
<dependency>
    <groupId>org.codehaus.jackson</groupId>
    <artifactId>jackson-mapper-asl</artifactId>
    <version>1.9.13</version>
</dependency>


web.xml

<servlet>
    <servlet-name>mvc-dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet
    </servlet-class>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>mvc-dispatcher</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/mvc-dispatcher-servlet.xml</param-value>
</context-param>
 
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener
    </listener-class>
</listener>

The servlet-name is 'mvc-dispatcher'. So we need a context xml with the name mvc-dispatcher-servlet.xml

mvc-dispatcher-servlet.xml


<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:context="http://www.springframework.org/schema/context" 
          xmlns:util="http://www.springframework.org/schema/util"  
          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         
                              http://www.springframework.org/schema/context 
                              http://www.springframework.org/schema/context/spring-context-3.0.xsd 
                              http://www.springframework.org/schema/util 
                              http://www.springframework.org/schema/util/spring-util.xsd">   

          <context:component-scan base-package="com.myorg" />
 
          <mvc:annotation-driven/>
  
          <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
                <property name="messageConverters">
                <util:list id="beanList">
                    <ref bean="stringHttpMessageConverter"/>        
                </util:list>
                </property>
          </bean>
   
          <bean id="stringHttpMessageConverter" 
                              class="org.springframework.http.converter.StringHttpMessageConverter"/>
 
          
          <bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
                 <property name="mediaTypes">
                 <map>
                    <entry key="html" value="text/html"/>
                    <entry key="json" value="application/json"/>
                 </map>
                 </property>
                 <property name="viewResolvers">
                      <list>
                      <bean class="org.springframework.web.servlet.view.BeanNameViewResolver"/>
                      <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
                          <property name="prefix" value="/WEB-INF/jsp/"/>
                          <property name="suffix" value=".jsp"/>
                      </bean>
                      </list>
                 </property>
                 <property name="defaultViews">
                    <list>
                        <bean class="org.springframework.web.servlet.view.json.MappingJacksonJsonView" />
                    </list>
                 </property>
         </bean>
</beans>

Things to know:

  • The namespaces in the beans element are very important as the spring container validates this xml against the xsd.
  • Every xmlns attribute should have a corresponding entry in the xsi:schemaLocation attribute. Here we have used mvc, context, util prefixes.
  • 'component-scan' element tells the spring container to look for com.myorg package for annotated controllers.
  • 'mvc:annotation-driven' tells the spring container that we are using annotated controllers.
  • InternalResourceViewResolver is used to indicate that the views are present in '/WEB-INF/view/' folder with jsp extension.
  • The ContentNegotiatingViewResolver is used to produce the correct format of response from our controllers. For Restful webservices, we will need response formats like JSON,html,xml etc. 
  • MappingJacksonJsonView is used to convert Java POJO to JSON format.


User POJO:

public class User implements Serializable{
    private static final long serialVersionUID = 10008L;
 
    private String firstName;
    private String lastName;
    private long userId;
 
    public User(String _first, String _last){
      this.firstName = _first;
      this.lastName  = _last;
    }
 
    public User(){  
    }
    
    //Setters and Getters
}  

User Service:

The service is a trivial implementation for brevity.


public interface UserService {
    public User createUser(User user) throws UserCreateException;
    public User getUser(String userId) throws UserNotFoundException;
    public void updateUser(User user) throws UserNotFoundException;
    public void deleteUser(String userId) throws UserNotFoundException;
}

Implementation is provided below. Note the @service annotation which is then injected into the controller using autowiring.


@Service
public class UserServiceImpl implements UserService{
    public User createUser(User user) throws UserCreateException{
       //implement here
       user.setUserId(111);
       return user;
    }

    public User getUser(String userId) throws UserNotFoundException{
       User user = new User();
       if(userId == null){
           throw new UserNotFoundException("Invalid User Id");
       }
       user.setFirstName("John");
       user.setLastName("Howard");
       return user;
    }

    public void updateUser(User user) throws UserNotFoundException{
       //implement here
    }

    public void deleteUser(String userId) throws UserNotFoundException{
      //implement here
    }
}

Exception classes


public class ServiceException extends Exception{
     private static final long serialVersionUID = 1L;
 
     public ServiceException(String _msg){
         super(_msg);
     }
}

public class AuthenticationException extends ServiceException{
    private static final long serialVersionUID = 1L;
  
    public AuthenticationException(String _msg){
        super(_msg);
    } 
}

public class UserCreateException extends ServiceException{
    private static final long serialVersionUID = 1L;
 
    public UserCreateException(String _msg){
        super(_msg);
    }
}
  
public class UserNotFoundException extends ServiceException{
    private static final long serialVersionUID = 1L;
 
    public UserNotFoundException(String _msg){
       super(_msg);
    }
}


UserController:


@Controller
public class UserController extends AbstractController{
 
    @Autowired
    private UserService userService;
 
    @RequestMapping(value="/restexample/users/user",method=RequestMethod.POST,
                    produces="application/json")
    @ResponseBody
    public User createUser(@RequestBody String body, HttpServletRequest _request) 
                           throws UserCreateException,AuthenticationException{
  
         authenticate(_request, "createUser");
  
         String firstName = _request.getParameter("firstName");
         String lastName = _request.getParameter("lastName");

         User user = new User(firstName,lastName);

         user = userService.createUser(user);

         return user;
    }
 
    /*Invoked depending on accept header*/
 
    @RequestMapping(value="/restexample/users/user/{userId}",method=RequestMethod.GET,
                    produces="application/json")   
    @ResponseBody
    public User getUser(@PathVariable String userId,HttpServletRequest _request) 
                        throws UserNotFoundException,AuthenticationException{
   
        authenticate(_request, "getUser");
        return userService.getUser(userId);
    }
 
    @RequestMapping(value="/restexample/users/user/{userId}",method=RequestMethod.GET)   
    @ResponseBody
    public String getUserAsHTML(@PathVariable String userId,HttpServletRequest _request) 
                               throws UserNotFoundException,AuthenticationException{

        authenticate(_request, "getUser");
        User user =  userService.getUser(userId);

        StringBuilder builder = new StringBuilder();
        builder.append("FirstName: ").append(user.getFirstName());
        builder.append("LastName: ").append(user.getLastName());
    
        return builder.toString();
    }
 
    @RequestMapping(value="/restexample/users/user/{userId}",method=RequestMethod.PUT)
    @ResponseBody
    public String updateUser(@PathVariable String userId,@RequestBody String body, 
                                  HttpServletRequest _request) 
                           throws UserNotFoundException,AuthenticationException{
   
        authenticate(_request, "updateUser");
        long userIdVal = convertStringToLong(userId);

        if(userIdVal < 1){
             throw new UserNotFoundException("Not a valid User Id: "+userId);
        }

        String firstName = _request.getParameter("firstName");
        String lastName = _request.getParameter("lastName");

        User user = new User(firstName,lastName);
        user.setUserId(userIdVal);

        userService.updateUser(user);
        return "success";
    }
 
    @RequestMapping(value="/restexample/users/user/{userId}",method=RequestMethod.DELETE)
    @ResponseBody
    public String deleteUser(@PathVariable String userId,@RequestBody String body, 
                                  HttpServletRequest _request) 
                           throws UserNotFoundException,AuthenticationException{

        authenticate(_request, "createUser");

        User user = new User();

        userService.deleteUser(userId);
        return "success";
    }
 
    public void authenticate(HttpServletRequest _request, String _serviceName) 
                             throws AuthenticationException{

        String hashKey = _request.getHeader("hashKey");
        boolean valid = true;
        //validate hashkey
        if(!valid){
             throw new AuthenticationException(_serviceName);
        }
    }
}


AbstractController:



public abstract class AbstractController {
 
  private final String USER_NOT_FOUND_ERROR_CODE = "1002";

  private final String USER_NOT_FOUND_ERROR_DESC = "The provided user cannot be found.";

  private final String AUTHENTICATION_ERROR_CODE = "1003";

  private final String AUTHENTICATION_ERROR_DESC = "You are not authorized to access this service.";

  private final String USER_CREATION_ERROR_CODE = "1001";

  private final String USER_CREATION_ERROR_DESC = "User creation failed. ";

  private final String UKNOWN_ERROR_CODE = "1000";

  private final String UKNOWN_ERROR_DESC = "An unknown error has occurred";

 
  @ExceptionHandler(ServiceException.class)
  @ResponseBody
  @ResponseStatus(HttpStatus.BAD_REQUEST)
  public RestError handleServiceException(ServiceException ex, HttpServletResponse _request){
      RestError error = new RestError();

      if(ex instanceof AuthenticationException){
        error.setErrorCode(AUTHENTICATION_ERROR_CODE);
        error.setErrrorDesc(AUTHENTICATION_ERROR_DESC);
      }
      else if(ex instanceof UserCreateException){
        error.setErrorCode(USER_CREATION_ERROR_CODE);
        error.setErrrorDesc(USER_CREATION_ERROR_DESC);
      }
      else if(ex instanceof UserNotFoundException){
        error.setErrorCode(USER_NOT_FOUND_ERROR_CODE);
        error.setErrrorDesc(USER_NOT_FOUND_ERROR_DESC);
      }
      else {
        error.setErrorCode(UKNOWN_ERROR_CODE);
        error.setErrrorDesc(UKNOWN_ERROR_DESC);
      }
      return error;
  }
}

RestError:


public class RestError implements Serializable{
    private static final long serialVersionUID = 198L;
 
    private String errorCode;
    private String errrorDesc;
 
    //Setters and getters
}

Things to know:


  • The controller extends from AbstractController which is explained later.
  • @RequestMapping can be used to map the Rest URI to the method. We can add parameters to the URI like {userId}
  • The parameters are mapped to variables through PathVariable
  • The method can declare what media type it is consuming/producing. For eg, produces="application/json"
  • The method can also declare what request type it is accepting through method=RequestMethod.POST/PUT/GET etc.
  • The accept header set by the client will be used to determine the method to be invoked.
  • The method can declare that it is returning a response in the Response body through @ResponseBody.
  • If the response type is a custom object like 'User', Jakson Mapping view will convert it to JSON format if required.
  • The authentication performed here is simple authentication by using a secret hash key (which is preagreed by client/server).This hash key is sent across in the request header. 
  • The same URI can be mapped to send across different response formats.
  • The getUserAsHTML() and getUser() map to same URI. However, getUserAsHTML() returns html content (the method can return a div content) whereas getUser() returns JSON content.


Handling exceptions:

The @ExceptionHandler annotation could be used to handle the exceptions. However, Spring requires every controller to have its own @ExceptionHandler annotated method.

To overcome this, we can have an abstract controller and have the exception handling at a common place. We can have different methods for each type of exception, or as in the example, we can handle the super exception class which is ServiceException and then handle individually.
If you are using Spring 3.2, you can use the @controlleradvice annotation and use any class for exception handling.

Rest Services generally should provide meaningful error messages and custom error codes when there is an error. At the same time, it should also prevent the internal exception hierarchy from reaching the client. In our example, the RestError object will which is returned by the exception handler will be converted to JSON format and returned back to the client.

Note that the error messages could be enumerated for better clarity.



Testing:

I have used Soap Client for testing. We can set the request type POST/GET etc and aslo the accept headers (application/json etc)

Following is the sample responses for a few requests:


Error Response:



HTTP response:
HTTP/1.1 400 Bad Request
Server: Apache-Coyote/1.1
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Tue, 20 Aug 2013 11:26:01 GMT
Connection: close

{"errorCode":"1003","errrorDesc":"You are not authorized to access this service. "}

JSON:
{
   "errorCode": "1003",
   "errrorDesc": "You are not authorized to access this service. "
}

Success response as JSON:

HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: application/json
Transfer-Encoding: chunked
Date: Tue, 20 Aug 2013 11:58:09 GMT

{"firstName":"John","lastName":"Howard","userId":111}

Comments

  1. thanks for ur post
    please tell me url for test via SOAP UI

    thanks again :-)

    ReplyDelete
  2. Hi Thanks for visit us:

    ARKA Softwares & Outsourcing is an IT Company focusing on software & Web development and providing offshore outsourcing solutions to enterprises worldwide.website designing company in usa

    ReplyDelete
  3. I really enjoy simply reading all of your weblogs. Simply wanted to inform you that you have people like me who appreciate your work. Definitely a great post I would like to read this
    angularjs online Training

    angularjs Training in marathahalli

    angularjs interview questions and answers

    angularjs Training in bangalore

    angularjs Training in bangalore

    angularjs online Training

    ReplyDelete
  4. This is most informative and also this post most user friendly and super navigation to all posts... Thank you so much for giving this information to me.

    rpa training in chennai
    rpa training in bangalore
    rpa course in bangalore
    best rpa training in bangalore
    rpa online training

    ReplyDelete
  5. I think you have a long story to share and i am glad after long time finally you cam and shared your experience.
    python course institute in bangalore
    python Course in bangalore
    python training institute in bangalore

    ReplyDelete
  6. Hi Thomas,
    I am not aware of a captcha plugin. I have not seen anybody using that in this platform.

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

    ReplyDelete
  8. For Devops Training in Bangalore visit : Devops training in Bangalore

    ReplyDelete
  9. Such great information for blogger iam a professional blogger thanks…

    Looking for Hadoop Admin Training in Bangalore, learn from Softgen Infotech provide Hadoop Admin Training on online training and classroom training. Join today!

    ReplyDelete

  10. Thank you for your post. This is excellent information. It is amazing and wonderful to visit your site.windows azure cloud computing training in bangalore


    ReplyDelete
  11. really it is an awesome information keep on doing...


    https://www.acte.in/angular-js-training-in-chennai
    https://www.acte.in/angular-js-training-in-annanagar
    https://www.acte.in/angular-js-training-in-omr
    https://www.acte.in/angular-js-training-in-porur
    https://www.acte.in/angular-js-training-in-tambaram
    https://www.acte.in/angular-js-training-in-velachery

    ReplyDelete
  12. wonderful article. Very interesting to read this article.I would like to thank you for the efforts you had made for writing this awesome article.Thanks for sharing such a good blog. You’re doing a great job. Keep posting like this useful info !! DevOps Training in Chennai | DevOps Training in anna nagar | DevOps Training in omr | DevOps Training in porur | DevOps Training in tambaram | DevOps Training in velachery

    ReplyDelete
  13. This is most informative and also this post most user friendly and super navigation to all posts... Thank you so much for giving this information to me.keep update your post
    AngularJS training in chennai | AngularJS training in anna nagar | AngularJS training in omr | AngularJS training in porur | AngularJS training in tambaram | AngularJS training in velachery

    ReplyDelete
  14. This is most informative and also this post most user friendly and super navigation to all posts... Thank you so much for giving this information to me.keep update your post
    AngularJS training in chennai | AngularJS training in anna nagar | AngularJS training in omr | AngularJS training in porur | AngularJS training in tambaram | AngularJS training in velachery

    ReplyDelete
  15. very nice blogs!!! i have to learning for lot of information for this sites...Sharing for wonderful information.Thanks for sharing this valuable information to our vision. You have posted a trust worthy blog keep sharing.
    IELTS Coaching in chennai

    German Classes in Chennai

    GRE Coaching Classes in Chennai

    TOEFL Coaching in Chennai

    Spoken english classes in chennai | Communication training

    ReplyDelete
  16. I am really happy with your blog because your article is very unique and powerful for new.
    Devops Training Institute in Pune
    Devops Training in Pune

    ReplyDelete
  17. Nice informative content. Thanks for sharing such worthy information.
    Asp.net MVC Hosting
    Advantages of MVC

    ReplyDelete
  18. The article you've shared here is fantastic because it provides some excellent information that will be incredibly beneficial to me. Thank you for sharing that. Keep up the good work. Metal Precision Casting

    ReplyDelete
  19. SIM cards are usually specific to a particular carrier, such as AT&T, Verizon, or Sprint. They can also be specific to a particular country. For example, a SIM card from the United Kingdom will not work in a device from the United States. Travel SIM Card

    ReplyDelete
  20. Vray 6.00.05 Crack is a commercial plug-in for third-party developers' 3D computer graphics software programs. VRay 6.00.05 Crack

    ReplyDelete
  21. Birthday Wishes For Older Brother — Happy Birthday dear brother, wishing you a fantastic one! I am grateful you are my brother because you have .https://wishesquotz.com/birthday-wishes-for-brother/

    ReplyDelete

Post a Comment

Popular posts from this blog

Pivotal Cloud Foundry (PCF) Integration with Elastic Cloud Storage (ECS)

Spring Integration - Bulk processing Example