Working with HandlerExceptionResolver

Spring MVC provides several approaches to exception handling. In Spring, one of the main exception handling constructs is the HandlerExceptionResolver (org.springframework.web.servlet.HandlerExceptionResolver) interface. Any objects that implement this interface can resolve exceptions thrown during Controller mapping or execution. HandlerExceptionResolver implementers are typically registered as beans in the web application context.

Spring MVC creates two such HandlerExceptionResolver implementations by default to facilitate exception handling:

  • ResponseStatusExceptionResolver is created to support the @ResponseStatus annotation
  • ExceptionHandlerExceptionResolver is created to support the @ExceptionHandler annotation

Time for action - adding a ResponseStatus exception

We will look at them one by one. First, the @ResponseStatus (org.springframework.web.bind.annotation.ResponseStatus) annotation; in Chapter 3, Control Your Store with Controllers we created a request mapping method to show products by category under the URI template: http://localhost:8080/webstore/market/products/{category}. If no products were found under the given category, we showed an empty web page, which is not correct semantically as we should show a HTTP status error to indicate that no products exist under the given category. Let's see how to do that with the help of the @ResponseStatus annotation:

  1. Create a class called NoProductsFoundUnderCategoryException under the com.packt.webstore.exception package in the src/main/java source folder. Now add the following code to it:
          package com.packt.webstore.exception; 
     
          import org.springframework.http.HttpStatus; 
          import org.springframework.web.bind.annotation
          .ResponseStatus; 
     
          @ResponseStatus(value=HttpStatus.NOT_FOUND, reason="No 
          products found under this category") 
          public class NoProductsFoundUnderCategoryException extends RuntimeException{ 
     
             private static final long serialVersionUID = 
             3935230281455340039L; 
          }
    
  2. Now open our ProductController class and modify the getProductsByCategory method as follows:
          @RequestMapping("/products/{category}") 
          public String getProductsByCategory(Model model, 
          @PathVariable("category") String category) { 
             List<Product> products = 
          productService.getProductsByCategory(category); 
     
             if (products == null || products.isEmpty()) { 
                throw new NoProductsFoundUnderCategoryException(); 
             } 
     
             model.addAttribute("products", products); 
             return "products"; 
          } 
    
  3. Now run our application and enter the URL http://localhost:8080/webstore/market/products/chargers. You will see a HTTP status error saying No products found under this category, as shown here:
    Time for action - adding a ResponseStatus exception

    Product category page showing the HTTP Status 404 for No products found under this category

What just happened?

In step 1, we created a runtime exception called NoProductsFoundUnderCategoryException to indicate that no products were found under the given category. One of the important constructs in the NoProductsFoundUnderCategoryException class is the @ResponseStatus annotation, which instructs the Spring MVC to return a specific HTTP status if this exception has been thrown from a request mapping method.

We can configure which HTTP status needs to be returned via the value attribute of the @ResponseStatus annotation; in our case we configured HttpStatus.NOT_FOUND (org.springframework.http.HttpStatus), which displays the familiar HTTP 404 response. The second attribute, reason, denotes the reason for the HTTP response error.

In step 2, we just modified the getProductsByCategory method in the ProductController class to check whether the product list for the given category is empty. If so, we simply throw the exception we created in step 1, which returns a HTTP Status 404 error to the client saying No products found under this category.

So finally in step 3, we fired the web request http://localhost:8080/webstore/market/products/chargers, which would try to look for products under the category chargers but, since we didn't have any products under the chargers category, we got the HTTP Status 404 error.

It's good that we can show the HTTP status error for products not found under a given category, but sometimes you may wish to have an error page where you want to show your error message in a more detailed manner.

For example, run our application and enter http://localhost:8080/webstore/market/product?id=P1234. You will be able to see a detailed View of the iPhone 6s—now change the product ID in the URL to an invalid one such as http://localhost:8080/webstore/market/product?id=P1000, and you will see an error page.

Time for action - adding an exception handler

We should show a nice error message saying No products found with the given product ID, so let's do that with the help of @ExceptionHandler:

  1. Create a class called ProductNotFoundException under the com.packt.webstore.exception package in the src/main/java source folder. Add the following code to it:
          package com.packt.webstore.exception; 
     
          public class ProductNotFoundException extends 
          RuntimeException{ 
     
             private static final long serialVersionUID =       
             -694354952032299587L; 
        
             private String productId; 
     
             public ProductNotFoundException(String productId) { 
                this.productId = productId; 
             } 
              
             public String getProductId() { 
                return productId; 
             } 
           
          } 
    
  2. Now open our InMemoryProductRepository class and modify the getProductById method as follows:
          @Override 
          public Product getProductById(String productID) { 
             String SQL = "SELECT * FROM PRODUCTS WHERE ID = :id";   
             Map<String, Object> params = new HashMap<>(); 
             params.put("id", productID);   
           
             try { 
                return jdbcTemplate.queryForObject(SQL, params, new       ProductMapper()); 
             } catch (DataAccessException e) { 
                throw new ProductNotFoundException(productID); 
             }    
          } 
    
  3. Add an exception handler method using the @ExceptionHandler (org.springframework.web.bind.annotation.ExceptionHandler) annotation, as follows, in the ProductController class:
          @ExceptionHandler(ProductNotFoundException.class) 
          public ModelAndView handleError(HttpServletRequest req,      
          ProductNotFoundException exception) { 
              ModelAndView mav = new ModelAndView(); 
              mav.addObject("invalidProductId", 
              exception.getProductId()); 
              mav.addObject("exception", exception); 
              mav.addObject("url",      
              req.getRequestURL()+"?"+req.getQueryString()); 
              mav.setViewName("productNotFound"); 
              return mav; 
          } 
    
  4. Finally, add one more JSP View file called productNotFound.jsp under the src/main/webapp/WEB-INF/views/ directory, add the following code snippets to it, and save it:
          <%@ taglib prefix="c" 
          uri="http://java.sun.com/jsp/jstl/core"%> 
          <%@ taglib prefix="spring" 
          uri="http://www.springframework.org/tags" %>  
     
          <html> 
          <head> 
          <meta http-equiv="Content-Type" content="text/html; 
          charset=ISO-8859-1"> 
          <link rel="stylesheet"       
          href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css
          /bootstrap.min.css"> 
          <title>Welcome</title> 
          </head> 
          <body> 
             <section> 
                <div class="jumbotron"> 
                   <div class="container"> 
                      <h1 class="alert alert-danger"> There is no 
                      product found with the Product id 
                      ${invalidProductId}</h1> 
                   </div> 
                </div> 
             </section> 
     
             <section> 
                <div class="container"> 
                   <p>${url}</p> 
                   <p>${exception}</p> 
                </div> 
     
                <div class="container"> 
                   <p> 
                      <a href="<spring:url value="/market/products" />" 
                      class="btn btn-primary"> 
                         <span class="glyphicon-hand-left glyphicon">
                         </span> products 
                      </a> 
                   </p> 
                </div> 
           
             </section> 
          </body> 
          </html> 
    
  5. Now run our application and enter http://localhost:8080/webstore/market/product?id=P1000. You will see an error page showing There is no product found with the Product id P1000 as follows:
    Time for action - adding an exception handler

    Product detail page showing a custom error page for the product ID P1000

What just happened?

We decided to show a custom-made error page instead of showing the raw exception in the case of a product not being found for a given product ID. So, in order to achieve that, in step 1 we just created a runtime exception called ProductNotFoundException to be thrown when the product is not found for the given product ID.

In step 2, we just modified the getProductById method of the InMemoryProductRepository class to check whether any products were found for the given product ID. If not, we simply throw the exception (ProductNotFoundException) we created in step 1.

In step 3, we added our exception handler method to handle ProductNotFoundException with the help of the @ExceptionHandler annotation. Within the handleError method, we just created a ModelAndView (org.springframework.web.servlet.ModelAndView) object and stored the requested invalid product ID, exception, and the requested URL, and returned it with the View name productNotFound:

@ExceptionHandler(ProductNotFoundException.class) 
public ModelAndView handleError(HttpServletRequest req, ProductNotFoundException exception) { 
    ModelAndView mav = new ModelAndView(); 
    mav.addObject("invalidProductId", exception.getProductId()); 
    mav.addObject("exception", exception); 
    mav.addObject("url", req.getRequestURL()+"?"+req.getQueryString()); 
    mav.setViewName("productNotFound"); 
    return mav; 
} 

Since we returned the ModelAndView object with the View name productNotFound, we must have a View file with the name productNotFound. That's why we created this View file (productNotFound.jsp) in step 4. productNotFound.jsp just contains a CSS-styled <h1> tag to show the error message and a link button to the product listing page.

So, whenever we request to show a product with an invalid ID such as http://localhost:8080/webstore/product?id=P1000, the ProductController class will throw the ProductNotFoundException, which will be handled by the handleError method and will show the custom error page (productNotFound.jsp).

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.17.208.206