Servlet传统异常处理
Servlet规范规定了当web应用发生异常时必须能够指明, 并确定了该如何处理, 规定了错误信息应该包含的内容和展示页面的方式.(详细可以参考servlet规范文档)
处理状态码<error-code>
处理异常信息<exception-type>
处理服务地址<location>
Spring MVC 处理方式所有的请求必然以某种方式转化为响应.
Spring中特定的异常将自动映射为特定的HTTP状态码
使用@ResponseStatus注解可以映射某一异常到特定的HTTP状态码
Controller方法上可以使用@ExceptionHandler注解使其用来处理异常
使用@ControllerAdvice 方式可以统一的方式处理全局异常
Spring boot 方式实现ErrorPageRegistrar: 确定是页面处理的路径必须固定,优点是比较通用
注册ErrorPage
实现ErrorPage对应的服务
源码分析一.接口HandlerExceptionResolver
该接口定义了Spring中该如何处理异常. 它只有一个方法resolveException(), 接口源码如下:
// 由对象实现的接口,这些对象可以解决在处理程序映射或执行期间引发的异常,在典型的情况下是错误视图。在应用程序上下文中,实现器通常被注册为bean。 // 错误视图类似于JSP错误页面,但是可以与任何类型的异常一起使用,包括任何已检查的异常,以及针对特定处理程序的潜在细粒度映射。 public interface HandlerExceptionResolver { @Nullable ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex); }Spring 为该接口提供了若干实现类如下:
HandlerExceptionResolverComposite 委托给其他HandlerExceptionResolver的实例列表 AbstractHandlerExceptionResolver 抽象基类 AbstractHandlerMethodExceptionResolver 支持HandlerMethod处理器的抽象基类 ExceptionHandlerExceptionResolver 通过 @ExceptionHandler 注解的方式实现的异常处理 DefaultHandlerExceptionResolver 默认实现, 处理spring预定义的异常并将其对应到错误码 ResponseStatusExceptionResolver 通过 @ResponseStatus 注解映射到错误码的异常 SimpleMappingExceptionResolver 允许将异常类映射到视图名二. DefaultHandlerExceptionResolver
这个类是Spring提供的默认实现, 用于将一些常见异常映射到特定的状态码. 这些状态码定义在接口HttpServletResponse中, 下面是几个状态码的代码片段
public interface HttpServletResponse extends ServletResponse { ... public static final int SC_OK = 200; public static final int SC_MOVED_PERMANENTLY = 301; public static final int SC_MOVED_TEMPORARILY = 302; public static final int SC_FOUND = 302; public static final int SC_UNAUTHORIZED = 401; public static final int SC_INTERNAL_SERVER_ERROR = 500; ... }实际上, DefaultHandlerExceptionResolver中并没有直接实现接口的resolveException方法, 而是实现了抽象类AbstractHandlerExceptionResolver的doResolveException()方法, 后者则在实现了接口的方法中委托给抽象方法doResolveException, 这个方法由子类去实现.
AbstractHandlerExceptionResolver的resolveException方法代码如下:
@Override @Nullable public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) { // 判断是否当前解析器可用于handler 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; } }接下来我们看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 NoHandlerFoundException) { return handleNoHandlerFoundException( (NoHandlerFoundException) ex, request, response, handler); } ..... } catch (Exception handlerEx) { if (logger.isWarnEnabled()) { logger.warn("Failure while trying to resolve exception [" + ex.getClass().getName() + "]", handlerEx); } } return null; }可以看到代码中使用了大量的分支语句, 实际上是将方法传入的异常类型通过instanceof运算符测试, 通过测试的转化为特定的异常. 并调用处理该异常的特定方法. 我们挑一个比如处理NoHandlerFoundException这个异常类的方法, 这个方法将异常映射为404错误.
protected ModelAndView handleNoHandlerFoundException(NoHandlerFoundException ex, HttpServletRequest request, HttpServletResponse response, @Nullable Object handler) throws IOException { pageNotFoundLogger.warn(ex.getMessage()); response.sendError(HttpServletResponse.SC_NOT_FOUND); //设置为404错误 return new ModelAndView(); //返回个空视图 }