Create and test REST services with Spring MVC

The first part of this example shows how to create and test REST services using Spring MVC. The controller contains CRUD operations on warehouses and its products. For this example, the repository is a stub that simulates access to the database.
The second part will access these services using the RestTemplate class and test them.
Source code available at the github spring rest repository.

Configuration

 The context configuration is quite simple. It is split in two xml files. The parent context:
<?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:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       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">
 
       <!-- Detects annotations like @Component, @Service, @Controller... -->
       <context:component-scan base-package="xpadro.tutorial.rest"/>
      
       <!-- Detects MVC annotations like @RequestMapping -->
       <mvc:annotation-driven/>
</beans>

 

And the servlet context, which contains the stub repository:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       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.0.xsd">
      
       <!-- The warehouse repository. Simulates the retrieval of data from the database -->
       <bean id="warehouseRepository" class="xpadro.tutorial.rest.repository.WarehouseRepositoryImpl"/>
</beans>

 

The web.xml file just contains basic Spring configuration:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">
  <display-name>SpringRestTest</display-name>
 
  <!-- Root context configuration -->
  <context-param>
       <param-name>contextConfigLocation</param-name>
       <param-value>classpath:xpadro/tutorial/rest/configuration/root-context.xml</param-value>
  </context-param>
 
  <!-- Loads Spring root context, which will be the parent context -->
  <listener>
       <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>
 
  <!-- Spring servlet-->
  <servlet>
       <servlet-name>springServlet</servlet-name>
       <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
       <init-param>
             <param-name>contextConfigLocation</param-name>
             <param-value>classpath:xpadro/tutorial/rest/configuration/app-context.xml</param-value>
       </init-param>
  </servlet>
  <servlet-mapping>
       <servlet-name>springServlet</servlet-name>
       <url-pattern>/spring/*</url-pattern>
  </servlet-mapping>
</web-app>

 

And finally the pom.xml with all the dependencies, which can be found in the repository pom.xml file.

Creating the RESTful services

 The controller has the following methods:
 
getWarehouse: Returns an existing warehouse.
@RequestMapping(value="/warehouses/{warehouseId}", method=RequestMethod.GET)
public @ResponseBody Warehouse getWarehouse(@PathVariable("warehouseId") int id) {
     return warehouseRepository.getWarehouse(id);
}

 

This method uses several MVC annotations, explained below:
                http://localhost:8080/myApp/spring/warehouses/1

 

 

addProduct: Adds a new product to an existing warehouse.
@RequestMapping(value="/warehouses/{warehouseId}/products", method=RequestMethod.POST)
@ResponseStatus(HttpStatus.CREATED)
public void addProduct(@PathVariable("warehouseId") int warehouseId, @RequestBody Product product, HttpServletRequest request, HttpServletResponse response) {
            
     warehouseRepository.addProduct(warehouseId, product);
     response.setHeader("Location", request.getRequestURL().append("/")
          .append(product.getId()).toString());
}

 

 

 Other methods are defined in this controller but won’t put them all here. You can look up the source code linked above.

Setting the exception handler

 You can have multiple exception handlers, each one mapped to one or more exception types. Using the @ExceptionHandler annotation allows you to handle exceptions raised by methods annotated with @RequestMapping. Instead of forwarding to a view, it allows you to set a response status code. For example:
@ResponseStatus(HttpStatus.NOT_FOUND)
@ExceptionHandler({ProductNotFoundException.class})
public voidhandleProductNotFound(ProductNotFoundException pe) {
     logger.warn("Product not found. Code: "+pe.getMessage());
}

 

 Testing the services

 The test class is as follows:
 
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={
       "classpath:xpadro/tutorial/rest/configuration/root-context.xml",
       "classpath:xpadro/tutorial/rest/configuration/app-context.xml"})
public class WarehouseTesting {
     private static final int WAREHOUSE_ID = 1;
     private static final int PRODUCT_ID = 4;
      
     private RestTemplate restTemplate = new RestTemplate();

     /**
      * Tests accessing to an existing warehouse
      */
     @Test
     public void getWarehouse() {
          String uri = "http://localhost:8081/rest_test/spring/warehouses/{warehouseId}";
          Warehouse warehouse = restTemplate.getForObject(uri, Warehouse.class, WAREHOUSE_ID);
          assertNotNull(warehouse);
          assertEquals("WAR_BCN_004", warehouse.getName());
     }
      
     /**
      * Tests the addition of a new product to an existing warehouse.
      */
     @Test
     public void addProduct() {
          //Adds the new product
          String uri = "http://localhost:8081/rest_test/spring/warehouses/{warehouseId}/products";
          Product product = new Product(PRODUCT_ID, "PROD_999");
          URI newProductLocation = restTemplate.postForLocation(uri, product, WAREHOUSE_ID);
            
          //Checks we can access to the created product
          Product createdProduct = restTemplate.getForObject(newProductLocation, Product.class);
          assertEquals(product, createdProduct);
          assertNotNull(createdProduct.getId());
     }

     /**
      * Tests the removal of an existing product
      */
     @Test
     public void removeProduct() {
          String uri = "http://localhost:8081/rest_test/spring/warehouses/{warehouseId}/products/{productId}";
          restTemplate.delete(uri, WAREHOUSE_ID, PRODUCT_ID);
            
          try {
               restTemplate.getForObject(uri, Product.class, WAREHOUSE_ID, PRODUCT_ID);
               throw new AssertionError("Should have returned an 404 error code");
          } catch (HttpClientErrorException e) {
               assertEquals(HttpStatus.NOT_FOUND, e.getStatusCode());
          }
     }
}

 

  • Accessing Restful services. HTTP Message converters