Spring MVC provides several ways for me to customize the handling of exceptions.

Reference for this article: Exception Handling in Spring MVC

Customize HTTP status codes for exceptions

By default, if we throw an exception in controller, Spring MVC will give the user a response of 500 pages with detailed error information.

If we want to change the HTTP status code for the exception, we can add @responseStatus annotation to the exception. With this annotation, we can set the HTTP status code and error message for the exception. Example:

@Controller
public class ExceptionController {
    @RequestMapping("/")
    public void test(){
        throw new NotFoundException();
    }
}
@ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "not found")
public class NotFoundException extends RuntimeException{
}Copy the code

Then request, you can find the page is different:

Controller level error interception handling

With the @responseStatus annotation, we can customize the HTTP status code and error message, but it’s not enough.

First, you can only set exceptions written by yourself, and you cannot extend existing exceptions.

Second, there is no way to customize the error page. The default error page is almost never used.

For both of these problems, you can add methods to the Controller to intercept and handle exceptions. The @ExceptionHandler annotation is required for the method. When annotated, the method intercepts exceptions thrown by the current Controller’s request-handling methods (the ones annotated by @RequestMapping). The exception interceptor method, meanwhile, returns the view used to render the error message. We can also use @responseStatus to customize the HTTP status code for existing exceptions.

@ Controller public class ExceptionHandlingController {/ / request processing method... @responseStatus (value= httpstatus.conflict, reason="Data integrity violation") // 409 @ExceptionHandler(DataIntegrityViolationException.class) public void Conflict () {/ / what also not stem} / / specifies the view to render the corresponding abnormal @ ExceptionHandler ({SQLException class, DataAccessException. Class}) public String  databaseError() { // Nothing to do. Returns the logical view name of an error page, passed // to the view-resolver(s) in usual way. // Note that the exception is NOT available to this view (it is not Added / / to the model) but see "Extending ExceptionHandlerExceptionResolver" / / below. / / what also not stem, Return "databaseError" because the exception was not added to the model; } / / intercept the Controller all exceptions thrown, the exception information at the same time through the ModelAndView to view / / or you can inherit ExceptionHandlerExceptionResolver, See @ExceptionHandler(exception.class) public ModelAndView handleError(HttpServletRequest req, Exception ex) { logger.error("Request: " + req.getRequestURL() + " raised " + ex); ModelAndView mav = new ModelAndView(); mav.addObject("exception", ex); mav.addObject("url", req.getRequestURL()); mav.setViewName("error"); return mav; }}Copy the code

Note that using the @ ExceptionHandler must specify which exception handling, otherwise you will quote exception: Java. Lang. IllegalArgumentException: No exception types mapped to {public java.lang.String XXController.exceptionHandler()}

Global exception handling

Controller-level exception control is powerful enough, but we can’t write a handleError method for every Controller, so we definitely need a global exception handling method. This requirement can be easily and directly implemented with @ControllerAdvice.

@controlleradvice is an annotation added by Spring3.2, which, as its name suggests, provides enhancements to Controller, The advice class annotated @ExceptionHandler, @initBinder, and @ModelAttribute methods can be applied to all controllers. The most commonly used is @ExceptionHandler. Instead of defining @ExceptionHandler in each Controller, we can declare an @ControllerAdvice class and define a unified @ExceptionHandler method.

For example, in the example above, @controllerAdvice would look like this:

@ControllerAdvice class GlobalControllerExceptionHandler { @ResponseStatus(HttpStatus.CONFLICT) // 409 @ ExceptionHandler (DataIntegrityViolationException. Class) public void handleConflict () {/ / what also not stem}}Copy the code

If you want to block all errors, just set Exception to exception.class as in the Controller level example above.

@ControllerAdvice class GlobalDefaultExceptionHandler { public static final String DEFAULT_ERROR_VIEW = "error"; @ExceptionHandler(value = Exception.class) public ModelAndView defaultErrorHandler(HttpServletRequest req, Throws Exception {ResponseStatus {ResponseStatus = ResponseStatus {ResponseStatus = ResponseStatus {ResponseStatus = ResponseStatus; Then rethrow the if (AnnotationUtils findAnnotation (um participant etClass (), ResponseStatus. Class)! = null) throw e; Mav = new ModelAndView(); mav.addObject("exception", e); mav.addObject("url", req.getRequestURL()); mav.setViewName(DEFAULT_ERROR_VIEW); return mav; }}Copy the code

Deeper interception

The Controller level and Controller Advice level interceptions mentioned above are annotation-based and advanced features. In the underlying implementation, Spring uses HandlerExceptionResolver.

All beans defined in the context of the DispatcherServlet application that implement the HandlerExceptionResolver interface are used for exception interception handling.

Take a look at the interface definition:

public interface HandlerExceptionResolver {
    ModelAndView resolveException(HttpServletRequest request, 
            HttpServletResponse response, Object handler, Exception ex);
}Copy the code

The handler argument is a reference to the Controller that threw the exception.

Spring implements several HandlerExceptionResolver classes that are the basis for several of the features mentioned above:

  • ExceptionHandlerExceptionResolver: Checks whether the exception can be matched to the Controller or Controller Advice@ExceptionHandlerMethod, if it can. (This is the class that implements the exception blocking methods mentioned above.)
  • ResponseStatusExceptionResolver: Checks whether the exception is removed@ResponseStatusAnnotation, if so, uses the information from the annotation to update the Response (the custom HTTP status code mentioned above is implemented with this feature)
  • DefaultHandlerExceptionResolver: Converts Spring exceptions to HTTP status codes (used internally by Spring)

These HandlerExceptionResolvers are executed in this order, the exception handling chain.

As you can see here, the resolveException method has no Model parameter in its signature, so the @ExceptionHandler method cannot inject this parameter. Therefore, the exception interception method has to create its own Model.

So, if you need to, you can implement your own exception handling chain by inherits HandlerExceptionResolver. The Ordered interface is then implemented so that you can control the processor’s order of execution.

SimpleMappingExceptionResolver

Spring provides a very convenient use HandlerExceptionResolver, called SimpleMappingExceptionResolver. It has many useful functions:

  • Map exception name to view name (exception name only needs to specify class name, not package name)
  • Specify a default error page
  • Print the exception to log
  • Specify the attribute name of exception to the view. The default attribute name is Exception. (@ExceptionHandlerMethod does not get exceptions by default, whileSimpleMappingExceptionResolverThe specified view can)

Usage:

<bean id="simpleMappingExceptionResolver" class= "org.springframework.web.servlet.handler.SimpleMappingExceptionResolver"> <property name="exceptionMappings"> <map> <entry key="DatabaseException" value="databaseError"/> <entry key="InvalidCreditCardException" value="creditCardError"/>  </map> </property> <! -- See note below on how this interacts with Spring Boot --> <property name="defaultErrorView" value="error"/> <property  name="exceptionAttribute" value="ex"/> <! -- Name of logger to use to log exceptions. Unset by default, so logging is disabled unless you set a value. --> <property name="warnLogCategory" value="example.MvcLogger"/> </bean>Copy the code

The Java Configuration:

@Configuration @EnableWebMvc // Optionally setup Spring MVC defaults (if you aren't using // Spring Boot & haven't specified @EnableWebMvc elsewhere) public class MvcConfiguration extends WebMvcConfigurerAdapter { @Bean(name="simpleMappingExceptionResolver") public SimpleMappingExceptionResolver createSimpleMappingExceptionResolver() { SimpleMappingExceptionResolver r = new SimpleMappingExceptionResolver(); Properties mappings = new Properties(); mappings.setProperty("DatabaseException", "databaseError"); mappings.setProperty("InvalidCreditCardException", "creditCardError"); r.setExceptionMappings(mappings); // None by default r.setDefaultErrorView("error"); // No default r.setExceptionAttribute("ex"); // Default is "exception" r.setWarnLogCategory("example.MvcLogger"); // No default return r; }... }Copy the code

Probably the most useful thing here is the TerrorView, which can be used to customise the default error page.

His inheritance SimpleMappingExceptionResolver to extend the functionality is also very common

  • Inheriting classes can set the default configuration in the constructor
  • coverbuildLogMessageThe method comes from the definition log message, which by default returns a fixed: Handler Execution resulted in exception
  • coverdoResolveExceptionMethod to pass more information you need to the error log

Examples are as follows:

public class MyMappingExceptionResolver extends SimpleMappingExceptionResolver { public MyMappingExceptionResolver() { / / enabled by default log setWarnLogCategory (MyMappingExceptionResolver. Class. GetName ()); } @Override public String buildLogMessage(Exception e, HttpServletRequest req) { return "MVC exception: " + e.getLocalizedMessage(); } @Override protected ModelAndView doResolveException(HttpServletRequest req, HttpServletResponse resp, Object handler, ModelAndView mav = super.doresolveException (req, resp, handler, ex); // Add additional fields to view v.addObject("url", request.getrequestURL ()); return mav; }}Copy the code

REST Exception Handling

In REST style, the error message returned is a JSON instead of a page. How to do that? In particular, define a class that returns information:

public class ErrorInfo { public final String url; public final String ex; public ErrorInfo(String url, Exception ex) { this.url = url; this.ex = ex.getLocalizedMessage(); }}Copy the code

Then add @responseBody to the error handler:

@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MyBadDataException.class)
@ResponseBody ErrorInfo
handleBadRequest(HttpServletRequest req, Exception ex) {
    return new ErrorInfo(req.getRequestURL(), ex);
}Copy the code

What special effects and when?

Spring gives us a lot of choices. How do we choose?

  • Consider using exceptions if you declare them yourself@ResponseStatusannotations
  • Other exceptions can be used@ControllerAdviceIn the@ExceptionHandlerMethod, or useSimpleMappingExceptionResolver
  • If the Controller needs to customize exceptions, you can add them to the Controller@ExceptionHandlerMethods.

If you mix these features, note that the @ExceptionHandler method in Controller has higher priority than the @ExceptionHandler method in @ControllerAdvice. If there are multiple @ControllerAdvice classes, The order of execution is uncertain.

The resources