A Technology Blog About Code Development, Architecture, Operating System, Hardware, Tips and Tutorials for Developers.

Saturday, December 15, 2012

RESTful Web Service with Spring 3.1

8:22:00 PM Posted by Satish Kumar , , , , , , , , , 9 comments
I posted lot of tutorials to create RESTful web services using different APIs like JAX-RS With GlassFish Jersey, JAX-RS With Jboss RESTEasy and JAX-RS With Apache CXF. I also posted the tutorials for Spring integration in Spring And Jersey, Spring and RESTEasy and Spring And CXF. But when it comes to RESTful web services, I personally like to use Spring 3.1 MVC. Some time back when RESTful web services were booming in industry, SpringSource developers released Spring 3.1 with REST support. They used the existing Spring MVC request mapping and took the help of JAX-RS 2.0 implementation for JSON and XML marshaling/ demarshaling. That is how they made it very simple.


This tutorial, I am going to demonstrate how to build a RESTful web service using Spring 3.1 MVC. Not only I will be creating a demo, along with that I will be demonstrating the CRUD operation with one of the model object. In this case I will be using FooBar as model object.

I am going to use the following tools and technologies in this turorial.
  • Spring 3.1.0.RELEASE
  • jackson-mapper-asl 1.9.9
  • jaxb-api 2.2.7
  • JDK 1.7
  • Tomcat 7.0
  • Maven
  • Eclipse 
I am going to take you to the following areas in this tutorial.
  • Service/Controller
  • Model Class
  • Spring MVC Context Configuration
  • Spring Integration With Web Container
  • Application Deployment
  • Testing
  • Source Code
Before start coding let's create a dynamic web project using the following maven command.

1
mvn archetype:generate -DgroupId=com.techiekernel.rest -DartifactId=SpringMVC-REST -DarchetypeArtifactId=maven-archetype-webapp -DinteractiveMode=false

After executing, the project will be get created with pom.xml file. As I am using JDK 7 and annotations, I have to specify the updated maven plugin. After giving the Jackson, JAXB and Spring dependency, the pom.xml looks as following.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
<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/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.techiekernel.rest</groupId>
  <artifactId>SpringMVC-REST</artifactId>
  <packaging>war</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>SpringMVC-REST Maven Webapp</name>
  <url>http://maven.apache.org</url>
  <properties>
    <jackson-mapper-asl.version>1.9.9</jackson-mapper-asl.version>
    <jaxb-api.version>2.2.7</jaxb-api.version>
  </properties>

  <dependencies>

    <!-- Spring 3 dependencies -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</artifactId>
      <version>3.1.0.RELEASE</version>
    </dependency>

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>3.1.0.RELEASE</version>
    </dependency>

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-web</artifactId>
      <version>3.1.0.RELEASE</version>
    </dependency>

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>3.1.0.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.codehaus.jackson</groupId>
      <artifactId>jackson-mapper-asl</artifactId>
      <version>${jackson-mapper-asl.version}</version>
    </dependency>
    <dependency>
      <groupId>javax.xml.bind</groupId>
      <artifactId>jaxb-api</artifactId>
      <version>${jaxb-api.version}</version>
    </dependency>
  </dependencies>
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-war-plugin</artifactId>
        <version>2.1.1</version>
      </plugin>
      <plugin>
        <artifactId>maven-compiler-plugin</artifactId>
        <configuration>
          <source>1.5</source>
          <target>1.5</target>
        </configuration>
      </plugin>
    </plugins>
    <finalName>SpringMVC-REST</finalName>
  </build>
</project>

Service/Controller:

In this case, we are going to write the web service in our Spring MVC controller. As you can see I have created POST, PUT, GET, DELETE methods to demonstrate the CRUD operations on FooBar. All the other configurations  are well self explanatory.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
package com.techiekernel.service;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

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 com.techiekernel.model.FooBar;

@Controller
@RequestMapping("/foobar")
public class FooBarService {

  static Set<FooBar> fooBars;

  static {
    fooBars = new HashSet<FooBar>();
    FooBar foobar = null;
    for (int i = 0; i < 10; i++) {
      foobar = new FooBar(i, "Techie Kernel " + i);
      fooBars.add(foobar);
    }
  }

  @RequestMapping(value = "/{foobarId}", method = RequestMethod.GET, headers = "Accept=application/xml, application/json", produces = {
      "application/json", "application/xml" })
  @ResponseBody
  public FooBar getFoobar(@PathVariable int foobarId) {
    for (FooBar foobar : fooBars) {
      if (foobar.getId() == foobarId)
        return foobar;
    }
    return null;
  }

  @RequestMapping(method = RequestMethod.GET, headers = "Accept=application/xml, application/json", produces = {
      "application/json", "application/xml" })
  @ResponseBody
  public Set<FooBar> getFoobars() {
    return fooBars;
  }

  @RequestMapping(value = "/{foobarId}", method = RequestMethod.PUT, headers = "Accept=application/xml, application/json", produces = {
      "application/json", "application/xml" }, consumes = {
      "application/json", "application/xml" })
  @ResponseBody
  public FooBar editFoobar(@RequestBody FooBar foobar,
      @PathVariable int foobarId) {
    for (FooBar foobar1 : fooBars) {
      if (foobarId == foobar1.getId()) {
        foobar1.setId(foobar.getId());
        foobar1.setName(foobar.getName());
        return foobar1;
      }
    }
    return null;
  }

  @RequestMapping(value = "/{foobarId}", method = RequestMethod.DELETE, headers = "Accept=application/xml, application/json", produces = {
      "application/json", "application/xml" })
  @ResponseBody
  public boolean deleteFoobar(@PathVariable int foobarId) {
    System.out.println("Delete call.");
    Iterator<FooBar> fooIterator = fooBars.iterator();
    while (fooIterator.hasNext()) {
      FooBar foobar = fooIterator.next();
      System.out.println(foobar);
      if (foobar.getId() == foobarId) {
        fooIterator.remove();
        return true;
      }
    }
    return false;
  }

  @RequestMapping(method = RequestMethod.POST, headers = "Accept=application/xml, application/json", produces = {
      "application/json", "application/xml" }, consumes = {
      "application/json", "application/xml" })
  @ResponseBody
  public boolean createFoobar(@RequestBody FooBar fooBar) {
    return fooBars.add(fooBar);
  }

}

Model Class:

This is the FooBar our model class which has to be marshaled and demarshaled by JAX-RS APIs. So be very care full to annotate @XmlRootElement and @JsonAutoDetect. These two annotations will mark the class for marshaling and marshaling.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
package com.techiekernel.model;

import javax.xml.bind.annotation.XmlRootElement;

import org.codehaus.jackson.annotate.JsonAutoDetect;

@XmlRootElement
@JsonAutoDetect
public class FooBar {
  int id;
  String name;

  public FooBar() {
    this.id = 1;
    this.name = "Techie Kernel";

  }
  
  public FooBar(int id, String name) {
    this.id = id;
    this.name = name;

  }

  public int getId() {
    return id;
  }

  public void setId(int id) {
    this.id = id;
  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  @Override
  public String toString() {
    return "FooBar [id=" + id + ", name=" + name + ", hashCode()="
        + hashCode() + "]";
  }
  
  @Override
  public int hashCode() {
    // TODO Auto-generated method stub
    return this.id;
  }
  
  @Override
  public boolean equals(Object obj) {
    if (obj instanceof FooBar) {
      if(this.id == ((FooBar)obj).id)
        return true;
    }
    return false;
  }
  
}

Spring MVC Context Configuration:

Now it's time to save the Spring MVC configuration in a xml file. Let's save the mvc-dispacher-servlet.xml in WEB-INF folder.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:context="http://www.springframework.org/schema/context"
  xmlns:mvc="http://www.springframework.org/schema/mvc" 
  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.1.xsd
        http://www.springframework.org/schema/context 
        http://www.springframework.org/schema/context/spring-context-3.1.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd">
  <mvc:annotation-driven />
  <context:component-scan base-package="com.techiekernel.service" />
</beans>

Spring Integration With Web Container:

The integration is going to be in web.xml. Register Spring “ContextLoaderListener” listener class and specify the Spring MVC dispacher servlet “org.springframework.web.servlet.DispatcherServlet“ as front controller for all requests.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<web-app id="WebApp_ID" version="2.4"
  xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee 
  http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
  <display-name>Spring MVC REST</display-name>

  <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>
  <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>
</web-app>

Application Deployment:

Now every thing is ready and time to build for creating a war file.

1
mvn clean install;

On successful completion of the above command a war file will be get created in target folder. Copy the war file to webapp folder of your tomcat and start the tomcat. For you people to test, I have put the application in the VMWare Cloud. You can access the application using this URL.

Testing:

I could have wrote a Junit test to test all the services. But for a better user experience, I would suggest you to test using one of the REST tools with browser. I am using REST Client addon from Mozzila Firefox. As I have told I am going to demonstrate the CRUD operations on model object FooBar, I will be doing the all four GET, POST, PUT and DELETE operations using the Cloud URL. I would request you not to hit the URLs blindly, as at some time there there will be a change in data or there will be no data, so better hit the GET URL first to see the data status. I will be using UNIX curl command here to give you a idea for testing. 

http://localhost:8080/SpringMVC-REST - localhost tomcat URL
http://springmvc-rest.cloudfoundry.com - Cloud URL


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
Get:
curl -i http://springmvc-rest.cloudfoundry.com/foobar -H "accept:application/json" -X GET
curl -i http://springmvc-rest.cloudfoundry.com/foobar/1 -H "accept:application/json" -X GET
curl -i http://springmvc-rest.cloudfoundry.com/foobar/1 -H "accept:application/xml" -X GET

PUT:
curl -i http://springmvc-rest.cloudfoundry.com/foobar/1 -H "accept:application/json" -H "content-Type:application/json" -X PUT -d "{  \"id\": 1,  \"name\": \"Techie Kernel-modified\"}"

POST:
curl -i http://springmvc-rest.cloudfoundry.com/foobar -H "accept:application/json" -H "content-Type:application/json" -X POST -d "{  \"id\": 10,  \"name\": \"Techie Kernel 10\"}"

DELETE:
curl -i http://springmvc-rest.cloudfoundry.com/foobar/10 -H "accept:application/json" -X DELETE


Source Code:

You can pull the code from GitHub.

9 comments:

  1. hi its nice that you have demonstrated the CRUD operations with REST. I have a question "How will bring security or authentication to the REST"

    ReplyDelete
  2. @Sahej:There is a concept of interceptor, which is supported my most of the JAX-RS implementations.. Interceptors work just like filters of a web application.. You can even configure interceptors for both in coming and outgoing messages..

    ReplyDelete
  3. Looks neat and easy. However the problem starts when it comes to large file processing with SAX and upload of binary data over clients. For this one can't find any examples or documentation .... I think Spring is still in its child stages when it comes to these tasks. Therefore I still recommend an integration with Jersey.

    ReplyDelete
  4. hello
    I wanted to execute the service with only Json objects as result, so i removed the appliacation/xml from all the services , and added












    in the mvc-dispatcher-servlet.xml ; but when i deploy and test http://localhost:8080/AzzimovUtilities/foobar/1 I have a Request method 'GET' not supported as result !

    what should I do to make it work only with Json ?

    ReplyDelete
  5. I wrote some json services at https://github.com/karasatishkumar/easylocate. Try that. Any problem post me. Must be a simple problem.

    ReplyDelete
  6. I tried running it but I am getting an error\
    java.lang.ClassNotFoundException: org.springframework.ws.transport.http.MessageDispatcherServlet
    at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1680)
    at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1526)
    at org.apache.catalina.core.StandardWrapper.loadServlet(StandardWrapper.java:1128)
    at org.apache.catalina.core.StandardWrapper.allocate(StandardWrapper.java:827)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:129)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:191)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:293)
    at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:859)
    at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:602)
    at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:489)
    at java.lang.Thread.run(Thread.java:662)
    Jun 7, 2013 2:21:10 PM org.apache.catalina.core.StandardWrapperValve invoke
    INFO: Servlet spring-ws is currently unavailable
    Jun 7, 2013 2:21:40 PM org.apache.catalina.core.StandardWrapperValve invoke
    INFO: Servlet spring-ws is currently unavailable

    ReplyDelete
  7. Looks like some problem with the jar versions..

    Can you download the project from github and do a clean build..

    ReplyDelete
  8. You save my life. This writing is so useful to me.

    ReplyDelete
  9. Best SpringMVC-Rest explanation code I've found, which just comes to the point. Don't know why other projects keep on mixing not related third party libraries and extra complexities as ORMs.

    Congratulations.

    ReplyDelete