HandlerExceptionResolver ,它是处理器异常解析器接口,将处理器( handler )执行时发生的异常,解析( 转换 )成对应的 ModelAndView 结果。
public interface HandlerExceptionResolver { /** * Try to resolve the given exception that got thrown during handler execution, * returning a {@link ModelAndView} that represents a specific error page if appropriate. * <p>The returned {@code ModelAndView} may be {@linkplain ModelAndView#isEmpty() empty} * to indicate that the exception has been resolved successfully but that no view * should be rendered, for instance by setting a status code. * @param request current HTTP request * @param response current HTTP response * @param handler the executed handler, or {@code null} if none chosen at the * time of the exception (for example, if multipart resolution failed) * @param ex the exception that got thrown during handler execution * @return a corresponding {@code ModelAndView} to forward to, * or {@code null} for default processing in the resolution chain */ @Nullable ModelAndView resolveException( HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex); }
至于它实现类,如下,
通过上面我们看到主要实现的是AbstractHandlerMethodExceptionResolver,四种实现类。
我们看下第一个实现类,AbstractHandlerExceptionResolver,它是所有直接解析异常类的父类,里面定义了通用的解析流程,并使用了模板模式,子类只需要覆盖相应的方法即可。
我们在下面来一一解读吧!
/** * Check whether this resolver is supposed to apply (i.e. if the supplied handler * matches any of the configured {@linkplain #setMappedHandlers handlers} or * {@linkplain #setMappedHandlerClasses handler classes}), and then delegate * to the {@link #doResolveException} template method. */ @Override @Nullable public ModelAndView resolveException( HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) { if (shouldApplyTo(request, handler)) { prepareResponse(ex, response); ModelAndView result = doResolveException(request, response, handler, ex); if (result != null) { // Print warn message when warn logger is not enabled... if (logger.isWarnEnabled() && (this.warnLogger == null || !this.warnLogger.isWarnEnabled())) { logger.warn("Resolved [" + ex + "]" + (result.isEmpty() ? "" : " to " + result)); } // warnLogger with full stack trace (requires explicit config) logException(ex, request); } return result; } else { return null; } }
接着,
注:模板方法,
虽然有点类似类的适配器模式,但是还是有很大的区别的。
好吧,解析resolveException方法里面我们不认识的方法。
我们来了解下其中的shouldApplyTo方法,
protected boolean shouldApplyTo(HttpServletRequest request, @Nullable Object handler) { if (handler != null) { if (this.mappedHandlers != null && this.mappedHandlers.contains(handler)) { return true; } if (this.mappedHandlerClasses != null) { for (Class<?> handlerClass : this.mappedHandlerClasses) { if (handlerClass.isInstance(handler)) { return true; } } } } // Else only apply if there are no explicit handler mappings. return (this.mappedHandlers == null && this.mappedHandlerClasses == null); }
通过上面的代码,我们了解到
接下来就是logException,它是默认的记录日志的方法,如下,
protected void logException(Exception ex, HttpServletRequest request) { if (this.warnLogger != null && this.warnLogger.isWarnEnabled()) { this.warnLogger.warn(buildLogMessage(ex, request)); } }
里面还有一个不了解的buildLogMessage方法,
protected String buildLogMessage(Exception ex, HttpServletRequest request) { return "Resolved [" + ex + "]"; }
LogException方法首先调用buildLogMessage创建了日志消息,然后使用了warmLogger将其记录下来。
我们了解下prepareResponse方法,
protected void prepareResponse(Exception ex, HttpServletResponse response) { if (this.preventResponseCaching) { preventCaching(response); } }
我们根据preventResponseCaching标示判断是否给response设置禁用缓存属性,preventResponseCaching默认为false。
protected void preventCaching(HttpServletResponse response) { response.addHeader(HEADER_CACHE_CONTROL, "no-store"); }
至于最后一个doResolveException方法是模板方法,子类使用它具体完成异常的解析工作。
好吧,第一个就这样解析完了。
我们看下第二个ExceptionHandlerExceptionResolver
public abstract class AbstractHandlerMethodExceptionResolver extends AbstractHandlerExceptionResolver { @Override protected boolean shouldApplyTo(HttpServletRequest request, @Nullable Object handler) { if (handler == null) { return super.shouldApplyTo(request, null); } else if (handler instanceof HandlerMethod) { HandlerMethod handlerMethod = (HandlerMethod) handler; handler = handlerMethod.getBean(); return super.shouldApplyTo(request, handler); } else { return false; } } @Override @Nullable protected final ModelAndView doResolveException( HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) { return doResolveHandlerMethodException(request, response, (HandlerMethod) handler, ex); } @Nullable protected abstract ModelAndView doResolveHandlerMethodException( HttpServletRequest request, HttpServletResponse response, @Nullable HandlerMethod handlerMethod, Exception ex); }
这样,我们来解析下这里,
/** * Find an {@code @ExceptionHandler} method and invoke it to handle the raised exception. */ @Override @Nullable protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerMethod handlerMethod, Exception exception) { ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception); if (exceptionHandlerMethod == null) { return null; } if (this.argumentResolvers != null) { exceptionHandlerMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers); } if (this.returnValueHandlers != null) { exceptionHandlerMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers); } ServletWebRequest webRequest = new ServletWebRequest(request, response); ModelAndViewContainer mavContainer = new ModelAndViewContainer(); try { if (logger.isDebugEnabled()) { logger.debug("Using @ExceptionHandler " + exceptionHandlerMethod); } Throwable cause = exception.getCause(); if (cause != null) { // Expose cause as provided argument as well exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, cause, handlerMethod); } else { // Otherwise, just the given exception as-is exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, handlerMethod); } } catch (Throwable invocationEx) { // Any other than the original exception is unintended here, // probably an accident (e.g. failed assertion or the like). if (invocationEx != exception && logger.isWarnEnabled()) { logger.warn("Failure in @ExceptionHandler " + exceptionHandlerMethod, invocationEx); } // Continue with default processing of the original exception... return null; } if (mavContainer.isRequestHandled()) { return new ModelAndView(); } else { ModelMap model = mavContainer.getModel(); HttpStatus status = mavContainer.getStatus(); ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, status); mav.setViewName(mavContainer.getViewName()); if (!mavContainer.isViewReference()) { mav.setView((View) mavContainer.getView()); } if (model instanceof RedirectAttributes) { Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes(); RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes); } return mav; } }
通过上面,我们了解到它仅是返回MainAndView,并没有对response进行设置,如果需要的话,可以自己在异常处理器中设置。
而且里面也没有什么不清晰的,所以,我们来看下第三个喽。。。
第三个DefaultHandlerExceptionResolver,
通过上图以及继承父类的因素,我们知道doResolveException是一个重要的方法,所以,
@Override @Nullable protected ModelAndView doResolveException( HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) { try { if (ex instanceof HttpRequestMethodNotSupportedException) { return handleHttpRequestMethodNotSupported( (HttpRequestMethodNotSupportedException) ex, request, response, handler); } else if (ex instanceof HttpMediaTypeNotSupportedException) { return handleHttpMediaTypeNotSupported( (HttpMediaTypeNotSupportedException) ex, request, response, handler); } else if (ex instanceof HttpMediaTypeNotAcceptableException) { return handleHttpMediaTypeNotAcceptable( (HttpMediaTypeNotAcceptableException) ex, request, response, handler); } else if (ex instanceof MissingPathVariableException) { return handleMissingPathVariable( (MissingPathVariableException) ex, request, response, handler); } else if (ex instanceof MissingServletRequestParameterException) { return handleMissingServletRequestParameter( (MissingServletRequestParameterException) ex, request, response, handler); } else if (ex instanceof ServletRequestBindingException) { return handleServletRequestBindingException( (ServletRequestBindingException) ex, request, response, handler); } else if (ex instanceof ConversionNotSupportedException) { return handleConversionNotSupported( (ConversionNotSupportedException) ex, request, response, handler); } else if (ex instanceof TypeMismatchException) { return handleTypeMismatch( (TypeMismatchException) ex, request, response, handler); } else if (ex instanceof HttpMessageNotReadableException) { return handleHttpMessageNotReadable( (HttpMessageNotReadableException) ex, request, response, handler); } else if (ex instanceof HttpMessageNotWritableException) { return handleHttpMessageNotWritable( (HttpMessageNotWritableException) ex, request, response, handler); } else if (ex instanceof MethodArgumentNotValidException) { return handleMethodArgumentNotValidException( (MethodArgumentNotValidException) ex, request, response, handler); } else if (ex instanceof MissingServletRequestPartException) { return handleMissingServletRequestPartException( (MissingServletRequestPartException) ex, request, response, handler); } else if (ex instanceof BindException) { return handleBindException((BindException) ex, request, response, handler); } else if (ex instanceof NoHandlerFoundException) { return handleNoHandlerFoundException( (NoHandlerFoundException) ex, request, response, handler); } else if (ex instanceof AsyncRequestTimeoutException) { return handleAsyncRequestTimeoutException( (AsyncRequestTimeoutException) ex, request, response, handler); } } catch (Exception handlerEx) { if (logger.isWarnEnabled()) { logger.warn("Failure while trying to resolve exception [" + ex.getClass().getName() + "]", handlerEx); } } return null; }
额。。。好多if else,你看这串代码的时候是不是也有这个感慨。这是也就说明了它的解析过程是根据异常类型的不同,使用了不同方法进行的。
虽然上面的具体解析方法很简单,主要是设置response的相关属性。但是我们还是要了解两个异常处理方法,也就是没找到处理器执行方法和request的Method类型不支持的处理异常。
protected ModelAndView handleNoHandlerFoundException(NoHandlerFoundException ex, HttpServletRequest request, HttpServletResponse response, @Nullable Object handler) throws IOException { pageNotFoundLogger.warn(ex.getMessage()); response.sendError(HttpServletResponse.SC_NOT_FOUND); return new ModelAndView(); }protected ModelAndView handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException ex, HttpServletRequest request, HttpServletResponse response, @Nullable Object handler) throws IOException { String[] supportedMethods = ex.getSupportedMethods(); if (supportedMethods != null) { response.setHeader("Allow", StringUtils.arrayToDelimitedString(supportedMethods, ", ")); } response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, ex.getMessage()); return new ModelAndView(); }
分析下上面,我们
第四个。。。
我们直接来看doResolveException吧。。。
@Override @Nullable protected ModelAndView doResolveException( HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) { try { if (ex instanceof ResponseStatusException) { return resolveResponseStatusException((ResponseStatusException) ex, request, response, handler); } ResponseStatus status = AnnotatedElementUtils.findMergedAnnotation(ex.getClass(), ResponseStatus.class); if (status != null) { return resolveResponseStatus(status, request, response, handler, ex); } if (ex.getCause() instanceof Exception) { ex = (Exception) ex.getCause(); return doResolveException(request, response, handler, ex); } } catch (Exception resolveEx) { logger.warn("Failure while trying to resolve exception [" + ex.getClass().getName() + "]", resolveEx); } return null; }至于为什么直接看这里,你看到@Override解析符了没。。。
这里的后者,你们知道是哪个方法吗。。。
答案在此揭开,
protected ModelAndView resolveResponseStatus(ResponseStatus responseStatus, HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) throws Exception { int statusCode = responseStatus.code().value(); String reason = responseStatus.reason(); return applyStatusAndReason(statusCode, reason, response); }
通过上面我们,也了解到ResponseStatusExceptionResolver的整体,应该要知道他是用来解析注释了@ResponseStatus异常(如自定义的注释了@ResponseStatus的异常)
我们看下第五个简单的解析吧。。。话说名为简单,但是解析它的内容是贼多滴。。。
看下doResolveException方法,
@Override @Nullable protected ModelAndView doResolveException( HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) { // Expose ModelAndView for chosen error view. // 根据异常查找显示错误页面的逻辑视图 String viewName = determineViewName(ex, request); if (viewName != null) { // Apply HTTP status code for error views, if specified. // Only apply it if we're processing a top-level request. // 检查是否配置了所找到的viewName对应的statusCode Integer statusCode = determineStatusCode(request, viewName); if (statusCode != null) { applyStatusCodeIfPossible(request, response, statusCode); } return getModelAndView(viewName, ex, request); } else { return null; } }
通过上面,我们发现
查找视图determineViewName方法如下,
@Nullable protected String determineViewName(Exception ex, HttpServletRequest request) { String viewName = null; // 如果异常在设置的excludedException中包含则返回null if (this.excludedExceptions != null) { for (Class<?> excludedEx : this.excludedExceptions) { if (excludedEx.equals(ex.getClass())) { return null; } } } // Check for specific exception mappings. // 调用findMatchingViewName方法实际查找 if (this.exceptionMappings != null) { viewName = findMatchingViewName(this.exceptionMappings, ex); } // Return default error view else, if defined. // 如果没有找到viewName并且配置defaultErrorView,则使用defaultErrorView if (viewName == null && this.defaultErrorView != null) { if (logger.isDebugEnabled()) { logger.debug("Resolving to default view '" + this.defaultErrorView + "'"); } viewName = this.defaultErrorView; } return viewName; }
分析上面的代码,我们分析
@Nullable protected String findMatchingViewName(Properties exceptionMappings, Exception ex) { String viewName = null; String dominantMapping = null; int deepest = Integer.MAX_VALUE; for (Enumeration<?> names = exceptionMappings.propertyNames(); names.hasMoreElements();) { String exceptionMapping = (String) names.nextElement(); int depth = getDepth(exceptionMapping, ex); if (depth >= 0 && (depth < deepest || (depth == deepest && dominantMapping != null && exceptionMapping.length() > dominantMapping.length()))) { deepest = depth; dominantMapping = exceptionMapping; viewName = exceptionMappings.getProperty(exceptionMapping); } } if (viewName != null && logger.isDebugEnabled()) { logger.debug("Resolving to view '" + viewName + "' based on mapping [" + dominantMapping + "]"); } return viewName; }
protected int getDepth(String exceptionMapping, Exception ex) { return getDepth(exceptionMapping, ex.getClass(), 0); }
接下来,再看下一个getDepth,
private int getDepth(String exceptionMapping, Class<?> exceptionClass, int depth) { if (exceptionClass.getName().contains(exceptionMapping)) { // Found it! return depth; } // If we've gone as far as we can go and haven't found it... if (exceptionClass == Throwable.class) { return -1; } return getDepth(exceptionMapping, exceptionClass.getSuperclass(), depth + 1); }
好吧,我们分析完了determineViewName,那么我们再回过头来分析下determineStatusCode方法吧,
@Nullable protected Integer determineStatusCode(HttpServletRequest request, String viewName) { if (this.statusCodes.containsKey(viewName)) { return this.statusCodes.get(viewName); } return this.defaultStatusCode; }
通过上面,我们发现
protected void applyStatusCodeIfPossible(HttpServletRequest request, HttpServletResponse response, int statusCode) { if (!WebUtils.isIncludeRequest(request)) { if (logger.isDebugEnabled()) { logger.debug("Applying HTTP status " + statusCode); } response.setStatus(statusCode); request.setAttribute(WebUtils.ERROR_STATUS_CODE_ATTRIBUTE, statusCode); } }
我们可以发现,在最后调用getModelAndView生成ModelAndView并返回。
protected ModelAndView getModelAndView(String viewName, Exception ex, HttpServletRequest request) { return getModelAndView(viewName, ex); } protected ModelAndView getModelAndView(String viewName, Exception ex) { ModelAndView mv = new ModelAndView(viewName); if (this.exceptionAttribute != null) { mv.addObject(this.exceptionAttribute, ex); } return mv; }
我们可以了解到生成过程其实也就是将ViewName设置为View,如果exceptionAttribute不为空则将异常添加到Model。
好了到了这里,我们也就分析完这四种实现类了。
我们分析的思路其实也就是从该组件的接口到实现。。。
但是你有想过我们是从哪里初始化它的?
参考资料:
看透springmvc源代码分析与实践 第16章 HandlerExceptionResolver