What is REST?
Representational State Transfer (REST) is a style of software architecture for distributed hypermedia systems such as the World Wide Web. The term Representational State Transfer was introduced and defined in 2000 by Roy Fielding in his doctoral dissertation.
Source: http://en.wikipedia.org/wiki/Representational_State_Transfer
The Representational State Transfer (REST) style is an abstraction of the architectural elements within a distributed hypermedia system. REST ignores the details of component implementation and protocol syntax in order to focus on the roles of components, the constraints upon their interaction with other components, and their interpretation of significant data elements. It encompasses the fundamental constraints upon components, connectors, and data that define the basis of the Web architecture, and thus the essence of its behavior as a network-based application.
Source: Architectural Styles and the Design of Network-based Software Architectures, a dissertation of Roy Thomas Fielding,
REST vs SOAP
Unlike SOAP-based web services, there is no "official" standard for RESTful web services. This is because REST is an architecture, unlike SOAP, which is a protocol. Even though REST is not a standard, a RESTful implementation such as the Web can use standards like HTTP, URL, XML, PNG, etc.
Source: http://en.wikipedia.org/wiki/Representational_State_Transfer
What is REST in Spring 3?
For a great introduction, please see the following links:
- REST in Spring 3: @MVC
- REST in Spring 3: RestTemplate
The Provider
Our application is a simple CRUD system for managing a list of persons. We'll start our project with the provider service which produces XML and JSON representations of our data.The Domain Layer
We'll be declaring two domain objects: Person and PersonListPerson.java
package org.krams.tutorial.domain; import javax.xml.bind.annotation.XmlRootElement; @XmlRootElement(name="person") public class Person { private Long id; private String firstName; private String lastName; private Double money; public Long getId() { return id; } public void setId(Long 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 Double getMoney() { return money; } public void setMoney(Double money) { this.money = money; } }Person is a simple POJO consisting of three properties. Notice we've annotated the class name with @XmlRootElement(name="person"). Its purpose is to help JAXB (the one responsible for marshalling/unmarshalling to XML) determine the root of our POJO.
PersonList.java
package org.krams.tutorial.domain; import java.util.List; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; @XmlRootElement(name="persons") public class PersonList { @XmlElement(required = true) public List<person> data; @XmlElement(required = false) public List<person> getData() { return data; } public void setData(List<person> data) { this.data = data; } }PersonList is another simple POJO. It's purpose is to wrap a list of Person objects.
Ideally we should be able to return a List
To resolve this issue with JAXB, we wrap our List
The Controller Layer
The controller layer is the most important section of the provider service because here's where we define the RESTful services available to our clients.ProviderController.java
package org.krams.tutorial.controller; import java.awt.image.BufferedImage; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.List; import javax.imageio.ImageIO; import org.apache.log4j.Logger; import org.krams.tutorial.domain.Person; import org.krams.tutorial.domain.PersonList; import org.krams.tutorial.service.PersonService; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; 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 javax.annotation.Resource; /** * REST service provider * * Only GET and POST will return values * PUT and DELETE will not. */ @Controller @RequestMapping("/provider") public class RestProviderController { protected static Logger logger = Logger.getLogger("controller"); @Resource(name="personService") private PersonService personService; @RequestMapping(value = "/person/{id}", method = RequestMethod.GET, headers="Accept=image/jpeg, image/jpg, image/png, image/gif") public @ResponseBody byte[] getPhoto(@PathVariable("id") Long id) { // Call service here try { // Retrieve image from the classpath InputStream is = this.getClass().getResourceAsStream("/bella.jpg"); // Prepare buffered image BufferedImage img = ImageIO.read(is); // Create a byte array output stream ByteArrayOutputStream bao = new ByteArrayOutputStream(); // Write to output stream ImageIO.write(img, "jpg", bao); logger.debug("Retrieving photo as byte array image"); return bao.toByteArray(); } catch (IOException e) { logger.error(e); throw new RuntimeException(e); } } @RequestMapping(value = "/person/{id}", method = RequestMethod.GET, headers="Accept=application/html, application/xhtml+xml") public String getPersonHTML(@PathVariable("id") Long id, Model model) { logger.debug("Provider has received request to get person with id: " + id); // Call service to here model.addAttribute("person",personService.get(id)); return "getpage"; } @RequestMapping(value = "/persons", method = RequestMethod.GET, headers="Accept=application/xml, application/json") public @ResponseBody PersonList getPerson() { logger.debug("Provider has received request to get all persons"); // Call service here PersonList result = new PersonList(); result.setData(personService.getAll()); return result; } @RequestMapping(value = "/person/{id}", method = RequestMethod.GET, headers="Accept=application/xml, application/json") public @ResponseBody Person getPerson(@PathVariable("id") Long id) { logger.debug("Provider has received request to get person with id: " + id); // Call service here return personService.get(id); } @RequestMapping(value = "/person", method = RequestMethod.POST, headers="Accept=application/xml, application/json") public @ResponseBody Person addPerson(@RequestBody Person person) { logger.debug("Provider has received request to add new person"); // Call service to here return personService.add(person); } @RequestMapping(value = "/person/{id}", method = RequestMethod.PUT, headers="Accept=application/xml, application/json") public @ResponseBody String updatePerson(@RequestBody Person person) { logger.debug("Provider has received request to edit person with id: " + id); // Call service here person.setId(id); return personService.edit(person).toString(); } @RequestMapping(value = "/person/{id}", method = RequestMethod.DELETE, headers="Accept=application/xml, application/json") public @ResponseBody String deleteEmployee(@PathVariable("id") Long id) { logger.debug("Provider has received request to delete person with id: " + id); // Call service here return personService.delete(id).toString(); } }
The URI Templates
Our controller has seven methods available. The first four are GET methods while the last three are POST, PUT, and DELETE methods.
The four GET methods correspond to the following:
1. Retrieving a single image based on a specific id
URL: http://localhost:8080/spring-rest-provider/krams/person/{id} Method: GET Accept Header: image/jpeg, image/jpg, image/png, image/gif
2. Retrieving an HTML page based on a specific id
URL: http://localhost:8080/spring-rest-provider/krams/person/{id} Method: GET Accept Header: application/html, application/xhtml+xml
3. Retrieving an XML or JSON containing a list of persons
URL: http://localhost:8080/spring-rest-provider/krams/person Method: GET Accept Header: application/xml, application/json
4. Retrieving an XML or JSON containing a single person
URL: http://localhost:8080/spring-rest-provider/krams/person/{id} Method: GET Accept Header: application/xml, application/json
The last three methods correspond to the remaining CRUD services:
5. Adding a new person via XML and JSON
URL: http://localhost:8080/spring-rest-provider/krams/person Method: POST Accept Header: application/xml, application/json
6. Editing an existing person via XML and JSON
URL: http://localhost:8080/spring-rest-provider/krams/person/{id} Method: PUT Accept Header: application/xml, application/json
7. Deleting an existing person via its id
URL: http://localhost:8080/spring-rest-provider/krams/person/{id} Method: DELETE Accept Header: application/xml, application/json
HTTP Methods
Notice for most of the methods, we're just reusing the same URL http://localhost:8080/spring-rest-provider/krams/person and the only part that differs are the suffix {id} and the HTTP methods:
Method | Purpose |
---|---|
GET | Retrieves a representation of the requested resource |
POST | Creates a new representation of the requested resource |
PUT | Updates an existing representation of the requested resource |
DELETE | Deletes an existing representation of the requested resource |
The Service Layer
The service layer is the one that does the actual processing of data. Here we're just managing an in-memory list to make the tutorial simpler to understand. Translating this to a real database isn't really that difficultPersonService.java
package org.krams.tutorial.service; import java.util.ArrayList; import java.util.List; import org.apache.log4j.Logger; import org.krams.tutorial.domain.Person; import org.springframework.stereotype.Service; @Service("personService") public class PersonService { protected static Logger logger = Logger.getLogger("service"); // In-memory list private List<Person> persons = new ArrayList<Person>(); public PersonService() { logger.debug("Init database"); // Create in-memory list Person person1 = new Person(); person1.setId(0L); person1.setFirstName("John"); person1.setLastName("Smith"); person1.setMoney(1000.0); Person person2 = new Person(); person2.setId(1L); person2.setFirstName("Jane"); person2.setLastName("Adams"); person2.setMoney(2000.0); Person person3 = new Person(); person3.setId(2L); person3.setFirstName("Jeff"); person3.setLastName("Mayer"); person3.setMoney(3000.0); persons.add(person1); persons.add(person2); persons.add(person3); } /** * Retrieves all persons */ public List<Person> getAll() { logger.debug("Retrieving all persons"); return persons; } /** * Retrieves a single person */ public Person get( Long id ) { logger.debug("Retrieving person with id: " + id); for (Person person:persons) { if (person.getId().longValue() == id.longValue()) { logger.debug("Found record"); return person; } } logger.debug("No records found"); return null; } /** * Adds a new person */ public Person add(Person person) { logger.debug("Adding new person"); try { // Find suitable id Long newId = 0L; Boolean hasFoundSuitableId = false; while(hasFoundSuitableId == false) { for (int i=0; i <persons.size(); i++) { if (persons.get(i).getId().longValue() == newId.longValue()) { newId++; break; } // Exit while loop if(i==persons.size()-1) { logger.debug("Assigning id: " + newId); hasFoundSuitableId = true; } } } // Assign new id person.setId(newId); // Add to list persons.add(person); logger.debug("Added new person"); return person; } catch (Exception e) { logger.error(e); return null; } } /** * Deletes an existing person */ public Boolean delete(Long id) { logger.debug("Deleting person with id: " + id); try { for (Person person:persons) { if (person.getId().longValue() == id.longValue()) { logger.debug("Found record"); persons.remove(person); return true; } } logger.debug("No records found"); return false; } catch (Exception e) { logger.error(e); return false; } } /** * Edits an existing person */ public Boolean edit(Person person) { logger.debug("Editing person with id: " + person.getId()); try { for (Person p:persons) { if (p.getId().longValue() == person.getId().longValue()) { logger.debug("Found record"); persons.remove(p); persons.add(person); return true; } } logger.debug("No records found"); return false; } catch (Exception e) { logger.error(e); return false; } } }
Configuration
Configuring REST support in Spring isn't really different from configuring your usual Spring MVC. That's because REST in Spring are additional "features to Spring MVC itself" (See Rest in Spring 3: @MVC blog).Here are all the XML configurations we're using for this project.
web.xml
<?xml version="1.0" encoding="UTF-8"?> <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"> <servlet> <servlet-name>spring</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>spring</servlet-name> <url-pattern>/krams/*</url-pattern> </servlet-mapping> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> </web-app>
spring-servlet.xml
<?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:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"> <!-- Declare a view resolver --> <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver" p:prefix="/WEB-INF/jsp/" p:suffix=".jsp" /> </beans>
applicationContext.xml
<?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:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="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/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd"> <!-- Activates various annotations to be detected in bean classes --> <context:annotation-config /> <!-- Scans the classpath for annotated components that will be auto-registered as Spring beans. For example @Controller and @Service. Make sure to set the correct base-package--> <context:component-scan base-package="org.krams.tutorial" /> <!-- Configures the annotation-driven Spring MVC Controller programming model. Note that, with Spring 3.0, this tag works in Servlet MVC only! --> <mvc:annotation-driven /> </beans>
Run the Application
To run the application, you'll need a client application, like a browser. Also you must access directly the URLs to reach a specific action.Browser Issues
By default, you can only send a GET request in your browser without resorting to special plugins or tools. When you type a URL in the browser's address field, you're actually specifying a GET request. Using a browser we can only retrieve XML and HTML representations of our GET request (again without resorting to special plugins). Try it out and see what your browser will return.On Chrome, typing the following URL
http://localhost:8080/spring-rest-provider/krams/person/1yields an XML document:
On Firefox, typing the following URL
http://localhost:8080/spring-rest-provider/krams/person/1yields an HTML document:
Testing with RESTClient
Using a browser we can only test the GET portion of the REST service. To fully test all methods, either we write a client application or reuse an existing client. Luckily for us there's a great Java client: RESTClientWhat is RESTClient?
RESTClient is a Java application to test RESTful webservices. It can be used to test variety of HTTP communications. From version 2.3 Beta 1, it has two executable Jars:
- GUI version (restclient-ui-X.jar download)
- Cli version for batch execution of .rcq files (restclient-cli-X.jar download)
Source: http://code.google.com/p/rest-client/
Let's start by running RESTClient
Retrieve a single record (GET method)
1. To retrieve a record, select the GET method:2. Click the Headers tab and enter the following information:
Key: Accept Value: application/xml or application/json
Make sure to hit the plus button to enter the values:
3. Enter the following URL:
http://localhost:8080/spring-rest-provider/krams/person/1You should see the following results depending on the Accept header you provided earlier:
XML result
JSON result
Add a new record (POST method)
1. Under the Method tab, select the POST method.2. Under the Headers tab, enter the following information:
Key: Accept Value: application/xml or application/json
3. Under the body tab, enter the following content (depending on the value field in step #2)
XML: <person> <firstName>Roy</firstName> <lastName>Jones</lastName> <money>5000</money> </person>JSON: { "firstName":"Roy" "lastName":"Jones" "money":5000 }
4. Enter the following URL:
http://localhost:8080/spring-rest-provider/krams/personYou should see the following results depending on the Accept header you provided earlier:
XML: <?xml version="1.0" encoding="UTF-8"?> <person> <firstName>Roy</firstName> <lastName>Jones</lastName> <money>5000.0</money> </person>JSON: { "firstName" : "Roy", "lastName" : "Jones", "money" : 5000.0 }
Update an existing record (PUT method)
1. Under the Method tab, select the PUT method.2. Under the Headers tab, enter the following information:
Key: Accept Value: application/xml or application/json
3. Under the body tab, enter the following content (depending on the value field in step #2)
XML: <person> <firstName>Roy</firstName> <lastName>Jones</lastName> <money>5000</money> </person>JSON: { "firstName":"Roy", "lastName":"Jones", "money":5000 }
4. Enter the following URL:
http://localhost:8080/spring-rest-provider/krams/personYou should see the following results depending on the Accept header you provided earlier:
XML: trueJSON: trueThis means we've successfully updated record #1 with the new information we've provided. To verify if the record has been updated, create a GET request with the following URL:
http://localhost:8080/spring-rest-provider/krams/provider/person/1Notice the number 1 at the end. That's the same number we used in the PUT method. You should see the updated record.
Delete an existing record (DELETE method)
1. Under the Method tab, select the DELETE method.2. Under the Headers tab, enter the following information:
Key: Accept Value: application/xml or application/json
3. Enter the following URL:
http://localhost:8080/spring-rest-provider/krams/person/1You should see the following results depending on the Accept header you provided earlier:
XML: trueJSON: trueThis means we've successfully deleted record #1. To verify if the record has been deleted, create a GET request with the following URL:
http://localhost:8080/spring-rest-provider/krams/provider/person/1
Notice the number 1 at the end. That's the same number we used in the DELETE method. You should see an empty result.
Conclusion
That's it. We've managed to create a REST service using Spring 3. We've also shown how we can retrieve different representations of our resource by using a unified URL with varying HTTP methods. We've also tested the application using RESTClient. In Part 2 of the tutorial, we'll explore how to develop a client application using Spring's RestTemplate support.To see Part 2 of this tutorial, click the following link: Spring 3: REST Web Service Provider and Client Tutorial (Part 2)
Download the project
You can access the project site at Google's Project Hosting at http://code.google.com/p/spring-rest-guide/
You can download the project as a Maven build. Look for the spring-rest-provider.zip in the Download sections.
You can run the project directly using an embedded server via Maven.
For Tomcat: mvn tomcat:run
For Jetty: mvn jetty:run
If you want to learn more about Spring MVC and integration with other technologies, feel free to read my other tutorials in the Tutorials section.
Share the joy:
|
Subscribe by reader Subscribe by email Share