1. An overview of the

The last article in Spring Boot series ten Spring MVC global exception handling summary introduced how to achieve the exception handling in Spring MVC, this article from the source perspective to understand the principle of Spring MVC exception handling, mainly including the following content:

  • HandlerExceptionResolver and common implementation classes, understand the default implementation of HandlerExceptionResolver use and source interpretation
  • Ordered Interface and how to customize HandlerExceptionResolver
  • Spring MVC exception handling HandlerExceptionResolver object initialization and processing process source interpretation

HandlerExceptionResolver and common implementation classes

2.1 HandlerExceptionResolver interface

HandlerExceptionResolver is an interface that handles exceptions thrown during network requests, but not exceptions thrown by the exception itself and exceptions thrown during view resolution

Below is the HandlerExceptionResolver class for Spring MVC’s default implementation

2.2. HandlerExceptionResolverComposite

Spring Boot startup when registered HandlerExceptionResolverComposite object by default. This class is just a composite class and does no real exception handling. When he catches an exception, he simply delegates exception polling to the HandlerExceptionResolver class registered in its property to handle the exception. If the result is not null, it is passed to the next handler

@Override public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { if (resolvers ! = null) { for (HandlerExceptionResolver handlerExceptionResolver : resolvers) { ModelAndView mav = handlerExceptionResolver.resolveException(request, response, handler, ex); if (mav ! = null) { return mav; } } } return null; }Copy the code

The default registered to HandlerExceptionResolverComposite attribute has the following three HandlerExceptionResolver, according to the priority arrangement is as follows:

  • ExceptionHandlerExceptionResolver
  • ResponseStatusExceptionResolver
  • DefaultHandlerExceptionResolver

The three HandlerExceptionResolvers are detailed below

2.3. ExceptionHandlerExceptionResolver

Use the @ExceptionHandler annotation method to handle exception classes. This class is responsible for the use of annotations to handle exceptions we described earlier. By default, this HandlerExceptionResolver has the highest priority.

The following is a ExceptionHandlerExceptionResolver runtime property values



Attribute exceptionHandlerAdviceCache: stored @ @ ExceptionHandler method in the Controller

Attribute exceptionHandlerAdviceCache: storage @ ControllerAdvice @ ExceptionHandler global method

Handle exceptions key code entry pass getExceptionHandlerMethod doResolveHandlerMethodException method can get the corresponding @ ExceptionHandler method, if there are found to perform this method

@Override protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod, Exception Exception) {/ / space to find the corresponding methods @ ExceptionHandler ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception); if (exceptionHandlerMethod == null) { return null; }... if (cause ! = null) {/ / execution exception handling methods exceptionHandlerMethod. InvokeAndHandle (webRequest mavContainer, exception, cause, handlerMethod); } else {/ / execution exception handling methods exceptionHandlerMethod. InvokeAndHandle (webRequest mavContainer, exception, handlerMethod); }}... }Copy the code

GetExceptionHandlerMethod method: To find the @ExceptionHandler method for a particular exception, first look for the corresponding handler method from the @Controller class that throws the exception. If no global @ExceptionHandler method is found from the @ControllerAdvice class, if the @ExceptionHandler method is found, This method is called to perform processing, otherwise null is returned

protected ServletInvocableHandlerMethod getExceptionHandlerMethod(HandlerMethod handlerMethod, Exception exception) { Class<? > handlerType = (handlerMethod ! = null ? handlerMethod.getBeanType() : null); if (handlerMethod ! = null) {// Find the corresponding handler from the @controller class itself, If you have find a cache first ExceptionHandlerMethodResolver resolver = this. ExceptionHandlerCache. Get (handlerType); if (resolver == null) { resolver = new ExceptionHandlerMethodResolver(handlerType); this.exceptionHandlerCache.put(handlerType, resolver); } Method method = resolver.resolveMethod(exception); if (method ! = null) {/ / method is called, returns the return new ServletInvocableHandlerMethod (handlerMethod) getBean (), method); }} // If @controllerAdvice does not find the global @ExceptionHandler method, if it does, Then call this method performs processing for (Entry < ControllerAdviceBean, ExceptionHandlerMethodResolver > Entry: this.exceptionHandlerAdviceCache.entrySet()) { if (entry.getKey().isApplicableToBeanType(handlerType)) { ExceptionHandlerMethodResolver resolver = entry.getValue(); Method method = resolver.resolveMethod(exception); if (method ! = null) { return new ServletInvocableHandlerMethod(entry.getKey().resolveBean(), method); } } } return null; }Copy the code

2.4. ResponseStatusExceptionResolver

Handle the exception with @responseStatus and convert the exception to the corresponding HTTP status code. @ ResponseStatus can be defined on the class of Excpetion subclasses, can also be defined on the @ ExceptionHandler annotation method (but this need to be careful to use, because of the high priority ExceptionHandlerExceptionResolver, This way may be ExceptionHandlerExceptionResolver override)

The doResolveException method looks for the @responseStatus annotation on the exception and acts as the ResponseStatus configured if the ResponseStatus is present

// protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler, ResponseStatus = ResponseStatus = ResponseStatus = ResponseStatus = ResponseStatus = ResponseStatus = AnnotatedElementUtils.findMergedAnnotation(ex.getClass(), ResponseStatus.class); if (responseStatus ! = null) {try {// if ResponseStatus, Responveresponsestatus (ResponseStatus, Request, response, handler, ex); ResponseStatus(ResponseStatus, request, response, handler, ex); } catch (Exception resolveEx) { logger.warn("Handling of @ResponseStatus resulted in Exception", resolveEx); }} else if (ex.getCause() instanceof Exception) {... } return null; }Copy the code

Set the HTTP status code returned and the reason based on the ResponseStatus value

protected ModelAndView resolveResponseStatus(ResponseStatus responseStatus, HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { int statusCode = responseStatus.code().value(); String reason = responseStatus.reason(); if (! Stringutils.haslength (reason)) {// set the returned HTTP statusCode response.senderror (statusCode); } else { String resolvedReason = (this.messageSource ! = null ? this.messageSource.getMessage(reason, null, reason, LocaleContextHolder.getLocale()) : reason); Response. sendError(statusCode, resolvedReason); } return new ModelAndView(); }Copy the code

2.5. DefaultHandlerExceptionResolver

The default HandlerExceptionResolver converts a specific exception to a standard HTTP status code.

Details are as follows: The exception name is displayed on the left, and the HTTP status code is displayed on the right

With code to explain this behavior, only list the HTTP error code conversion NoSuchRequestHandlingMethodException related code, form other similar exception handling

Exception handling entry doResolveException method, if discovery is unusual is NoSuchRequestHandlingMethodException handleNoSuchRequestHandlingMethod call the method

HTTP / / for NoSuchRequestHandlingMethodException transformation error bigger sizes protected ModelAndView doResolveException (it request, HttpServletResponse response, Object handler, Exception ex) { try { if (ex instanceof org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException) { return handleNoSuchRequestHandlingMethod((org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException)  ex, request, response, handler); {} else if (ex instanceof HttpRequestMethodNotSupportedException)... } else if... }Copy the code

HandleNoSuchRequestHandlingMethod method returns a 404 error code and error message

protected ModelAndView handleNoSuchRequestHandlingMethod(org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException ex,
    HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {

    pageNotFoundLogger.warn(ex.getMessage());
    response.sendError(HttpServletResponse.SC_NOT_FOUND);
    return new ModelAndView();
    }
Copy the code

2.6. Ordered and implements the custom HandlerExceptionResolver class

Each specific HandlerExceptionResolver implements the Ordered interface to define the order of execution. The smaller the order value, the higher the priority of execution. To implement your own HandlerExceptionResolver, only two conditions need to be met:

  • Implement the HandlerExceptionResolver and Ordered interfaces
  • Annotate this class with @component annotations to ensure that the corresponding object is created when Spring starts. See the spring Boot series 10 Spring MVC Global Exception Handling summary in the previous article

3. Interpretation of Spring MVC exception handling source code

3.1. How to initialize HandlerExceptionResolver when Spring MVC starts

When Spring MVC starts, initialize all handlerExceptionResolvers into the Spring container. Configuration in the initialization WebMvcConfigurationSupport Bean, will create HandlerExceptionResolverComposite object, the object includes three HandlerExceptionResolver, when he catch exceptions, These three HandlerExceptionResolver are successively used for processing, as follows:

Public class WebMvcConfigurationSupport implements ApplicationContextAware, ServletContextAware {... @Bean public HandlerExceptionResolver handlerExceptionResolver() { List<HandlerExceptionResolver> exceptionResolvers = new ArrayList<HandlerExceptionResolver>(); configureHandlerExceptionResolvers(exceptionResolvers); If (exceptionResolvers. IsEmpty ()) {/ / add default HandlerExceptionResolver class addDefaultHandlerExceptionResolvers(exceptionResolvers); } extendHandlerExceptionResolvers(exceptionResolvers); HandlerExceptionResolverComposite composite = new HandlerExceptionResolverComposite(); composite.setOrder(0); composite.setExceptionResolvers(exceptionResolvers); return composite; } / / add default HandlerExceptionResolverComposite and registration to the object of HandlerExceptionResolverComposite protected final void AddDefaultHandlerExceptionResolvers (List < HandlerExceptionResolver > exceptionResolvers) {/ / create ExceptionHandlerExceptionResolver() ExceptionHandlerExceptionResolver exceptionHandlerResolver = createExceptionHandlerExceptionResolver(); exceptionHandlerResolver.setContentNegotiationManager(mvcContentNegotiationManager()); exceptionHandlerResolver.setMessageConverters(getMessageConverters()); exceptionHandlerResolver.setCustomArgumentResolvers(getArgumentResolvers()); exceptionHandlerResolver.setCustomReturnValueHandlers(getReturnValueHandlers()); if (jackson2Present) { exceptionHandlerResolver.setResponseBodyAdvice( Collections.<ResponseBodyAdvice<? >>singletonList(new JsonViewResponseBodyAdvice())); } exceptionHandlerResolver.setApplicationContext(this.applicationContext); exceptionHandlerResolver.afterPropertiesSet(); exceptionResolvers.add(exceptionHandlerResolver); / / create ResponseStatusExceptionResolver ResponseStatusExceptionResolver responseStatusResolver = new ResponseStatusExceptionResolver(); responseStatusResolver.setMessageSource(this.applicationContext); exceptionResolvers.add(responseStatusResolver); / / create DefaultHandlerExceptionResolver exceptionResolvers. Add (new DefaultHandlerExceptionResolver ()); } protected ExceptionHandlerExceptionResolver createExceptionHandlerExceptionResolver() { return new ExceptionHandlerExceptionResolver(); }}Copy the code

3.2. DispatcherServlet

The entry class, which is a Servlet, is the distribution point for all requests following the source code in this class

3.2.1. The initialization

The onRefresh() method is triggered when the DispatcherServlet is initialized, which calls the initStrategies method to initialize the whole DispatcherServlet. Which initHandlerExceptionResolvers () will initialize HandlerExceptionResolvers object

protected void initStrategies(ApplicationContext context) { initMultipartResolver(context); initLocaleResolver(context); initThemeResolver(context); initHandlerMappings(context); initHandlerAdapters(context); / / object initialization HandlerExceptionResolvers initHandlerExceptionResolvers (context); initRequestToViewNameTranslator(context); initViewResolvers(context); initFlashMapManager(context); }Copy the code

From the Spring container ApplicationContext find all HandlerExceptionResolvers object, will be saved to the object properties HandlerExceptionResolvers. To insert HandlerExceptionResolver into Spring MVC, we need to implement HandlerExceptionResolver and Ordered. Annotate this class with an annotation like @Component

private void initHandlerExceptionResolvers(ApplicationContext context) { this.handlerExceptionResolvers = null; If (this. DetectAllHandlerExceptionResolvers) {/ / from the Spring container ApplicationContext find all HandlerExceptionResolvers object Map<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils .beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false); if (! Matchingbeans.isempty ()) {// Convert a Map to a List, Save to the attribute handlerExceptionResolvers enclosing handlerExceptionResolvers = new ArrayList<HandlerExceptionResolver>(matchingBeans.values()); / / value in the HandlerExceptionResolvers use according to the order interface to sort AnnotationAwareOrderComparator. Sort (enclosing HandlerExceptionResolvers); }}...Copy the code

At this point the initialization is complete

3.2.2. Exception handling process

If @requestMapping is used to throw an exception, the exception handling process is displayed

All do* methods of doPost, doGet, etc., execute the following method: find the actual business processing logic and process it.

The following code finds the actual HandlerAdapter object for this request, processes it, and finally calls processDispatchResult to process the result, which is what we care about

Protected void doDispatch(HttpServletRequest Request, HttpServletResponse Response) throws Exception {... try { ModelAndView mv = null; Exception dispatchException = null; try { processedRequest = checkMultipart(request); multipartRequestParsed = (processedRequest ! = request); // Determine handler for the current request. mappedHandler = getHandler(processedRequest); if (mappedHandler == null || mappedHandler.getHandler() == null) { noHandlerFound(processedRequest, response); return; } // Determine handler adapter for the current request. HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); // Process last-modified header, if supported by the handler. String method = request.getMethod(); boolean isGet = "GET".equals(method); if (isGet || "HEAD".equals(method)) { long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); if (logger.isDebugEnabled()) { logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified); } if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { return; } } if (! mappedHandler.applyPreHandle(processedRequest, response)) { return; } // Actually invoke the handler. mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); if (asyncManager.isConcurrentHandlingStarted()) { return; } applyDefaultViewName(processedRequest, mv); mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception ex) { dispatchException = ex; } catch (Throwable err) {As of 4.3, we're processing Errors thrown from handler methods As well, // making them available for @ExceptionHandler methods and other scenarios. dispatchException = new NestedServletException("Handler dispatch failed", err); } processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); }... }Copy the code

Process the result of the business execution, and the end of the process could be ModelAndView or Exception. If the result is Exception, it needs to be converted to ModelAndView through the HandlerExceptionResolver mentioned in this article. The result is then returned to the requester according to the ModelAndView

private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception { boolean errorView = false; if (exception ! = null) { if (exception instanceof ModelAndViewDefiningException) { logger.debug("ModelAndViewDefiningException encountered", exception); mv = ((ModelAndViewDefiningException) exception).getModelAndView(); } else {// If the return value is an exception, convert the HandlerExceptionResolver to ModelAndView Object handler = (mappedHandler! = null ? mappedHandler.getHandler() : null); mv = processHandlerException(request, response, handler, exception); errorView = (mv ! = null); } } // Did the handler return a view to render? // Perform subsequent operations according to ModelAndView if (mv! = null && ! mv.wasCleared()) { render(mv, request, response); if (errorView) { WebUtils.clearErrorRequestAttributes(request); }}... }Copy the code

When an exception occurs, the DispatcherServlet polling calls HandlerExceptionResolver until the exception is converted to ModelAndView

protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { // Check registered HandlerExceptionResolvers... ModelAndView exMv = null; for (HandlerExceptionResolver handlerExceptionResolver : this.handlerExceptionResolvers) { exMv = handlerExceptionResolver.resolveException(request, response, handler, ex); if (exMv ! = null) { break; }}... .}Copy the code

The exception handling is complete