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
- Service/Controller
- Model Class
- Spring MVC Context Configuration
- Spring Integration With Web Container
- Application Deployment
- Testing
- Source Code
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.
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@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..
ReplyDeleteLooks 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.
ReplyDeletehello
ReplyDeleteI 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 ?
I wrote some json services at https://github.com/karasatishkumar/easylocate. Try that. Any problem post me. Must be a simple problem.
ReplyDeleteI tried running it but I am getting an error\
ReplyDeletejava.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
Looks like some problem with the jar versions..
ReplyDeleteCan you download the project from github and do a clean build..
You save my life. This writing is so useful to me.
ReplyDeleteBest 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.
ReplyDeleteCongratulations.