【精选】springboot-异常处理使用与原理解析

241 阅读11分钟

目录

一、springboot的默认异常处理规则

1.浏览器访问不存在的资源会返回404页面

2.用postman访问不存在的资源会响应json

 3.如果用浏览器访问资源后台报错,会响应500错误页面

4.如果用postman访问资源后台报错,会响应json数据

​ 

二、自定义错误页面

1.设置自定义错误页面

2.500页面取出错误信息

三、异常处理的自动配置原理

1.ErrorMvcAutoConfiguration

2.BasicErrorController

3.DefaultErrorViewResolver

4.DefaultErrorAttributes

5.总结

四、默认异常处理流程

1.springmvc处理逻辑参考博文

2.DispatcherServlet的doDispatch方法

3.视图解析流程

4.处理handler发生的异常

5.系统默认的异常解析器

6.如果没有任何处理器能处理异常

五、自定义错误处理逻辑

1.自定义错误页面

2.@ControllerAdvice+@ExceptionHandler处理全局异常(推荐)

3.@ResponseStatus+自定义异常

4.Spring底层的异常

5.自定义异常解析器


一、springboot的默认异常处理规则

1.浏览器访问不存在的资源会返回404页面

2.用postman访问不存在的资源会响应json

 3.如果用浏览器访问资源后台报错,会响应500错误页面

4.如果用postman访问资源后台报错,会响应json数据

 

 

二、自定义错误页面

1.设置自定义错误页面

(1)在静态资源目录下(/static (or /public or /resources or /META-INF/resources),或者模板目录下(/templates) 放一个/error目录。

然后在里面放上对应的4xx.html、5xx.html。

再在浏览器访问4xx错误、5xx错误,就会显示新增的4xx.html、5xx.html页面。

2.500页面取出错误信息

会将以下的信息放到请求域中。

${message} // 错误信息
${trace} // 堆栈信息
${status} // 错误码

 

三、异常处理的自动配置原理

1.ErrorMvcAutoConfiguration

ErrorMvcAutoConfiguration 自动配置了默认的异常处理规则

@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class })
// Load before the main WebMvcAutoConfiguration so that the error View is available
// 在mvc配置好之后再配置该类
@AutoConfigureBefore(WebMvcAutoConfiguration.class)
// 绑定一些配置属性
@EnableConfigurationProperties({ ServerProperties.class, ResourceProperties.class, WebMvcProperties.class })
public class ErrorMvcAutoConfiguration {

   private final ServerProperties serverProperties;

   public ErrorMvcAutoConfiguration(ServerProperties serverProperties) {
      this.serverProperties = serverProperties;
   }

   // 注册一个DefaultErrorAttributes组件,id为errorAttributes,实现了ErrorAttributes, HandlerExceptionResolver, Ordered接口
   @Bean
   @ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)
   public DefaultErrorAttributes errorAttributes() {
      return new DefaultErrorAttributes();
   }

    // 注册一个BasicErrorController,id为basicErrorController
   @Bean
   @ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT)
   public BasicErrorController basicErrorController(ErrorAttributes errorAttributes,
         ObjectProvider<ErrorViewResolver> errorViewResolvers) {
      return new BasicErrorController(errorAttributes, this.serverProperties.getError(),
            errorViewResolvers.orderedStream().collect(Collectors.toList()));
   }

   // 错误页的定制化器
   @Bean
   public ErrorPageCustomizer errorPageCustomizer(DispatcherServletPath dispatcherServletPath) {
      return new ErrorPageCustomizer(this.serverProperties, dispatcherServletPath);
   }

   @Bean
   public static PreserveErrorControllerTargetClassPostProcessor preserveErrorControllerTargetClassPostProcessor() {
      return new PreserveErrorControllerTargetClassPostProcessor();
   }

   @Configuration(proxyBeanMethods = false)
   static class DefaultErrorViewResolverConfiguration {

      private final ApplicationContext applicationContext;

      private final ResourceProperties resourceProperties;

      DefaultErrorViewResolverConfiguration(ApplicationContext applicationContext,
            ResourceProperties resourceProperties) {
         this.applicationContext = applicationContext;
         this.resourceProperties = resourceProperties;
      }
      // 配置一个错误视图解析器
      @Bean
      @ConditionalOnBean(DispatcherServlet.class)
      @ConditionalOnMissingBean(ErrorViewResolver.class)
      DefaultErrorViewResolver conventionErrorViewResolver() {
         return new DefaultErrorViewResolver(this.applicationContext, this.resourceProperties);
      }
   }

   @Configuration(proxyBeanMethods = false)
   @ConditionalOnProperty(prefix = "server.error.whitelabel", name = "enabled", matchIfMissing = true)
   @Conditional(ErrorTemplateMissingCondition.class)
   protected static class WhitelabelErrorViewConfiguration {

      private final StaticView defaultErrorView = new StaticView();
      // 容器中还会放一个View组件,id为error,该View会响应默认错误页
      @Bean(name = "error")
      @ConditionalOnMissingBean(name = "error")
      public View defaultErrorView() {
         return this.defaultErrorView;
      }

      // If the user adds @EnableWebMvc then the bean name view resolver from
      // WebMvcAutoConfiguration disappears, so add it back in to avoid disappointment.
      // 容器中放组件 BeanNameViewResolver(视图解析器);按照返回的视图名作为组件的id去容器中找View对象。
      @Bean
      @ConditionalOnMissingBean
      public BeanNameViewResolver beanNameViewResolver() {
         BeanNameViewResolver resolver = new BeanNameViewResolver();
         resolver.setOrder(Ordered.LOWEST_PRECEDENCE - 10);
         return resolver;
      }

   }

   /**
    * {@link SpringBootCondition} that matches when no error template view is detected.
    */
   private static class ErrorTemplateMissingCondition extends SpringBootCondition {

      @Override
      public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
         ConditionMessage.Builder message = ConditionMessage.forCondition("ErrorTemplate Missing");
         TemplateAvailabilityProviders providers = new TemplateAvailabilityProviders(context.getClassLoader());
         TemplateAvailabilityProvider provider = providers.getProvider("error", context.getEnvironment(),
               context.getClassLoader(), context.getResourceLoader());
         if (provider != null) {
            return ConditionOutcome.noMatch(message.foundExactly("template from " + provider));
         }
         return ConditionOutcome.match(message.didNotFind("error template view").atAll());
      }
   }

   /**
    * Simple {@link View} implementation that writes a default HTML error page.响应默认错误页
    */
   private static class StaticView implements View {

      private static final MediaType TEXT_HTML_UTF8 = new MediaType("text", "html", StandardCharsets.UTF_8);

      private static final Log logger = LogFactory.getLog(StaticView.class);

      @Override
      public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)
            throws Exception {
         if (response.isCommitted()) {
            String message = getMessage(model);
            logger.error(message);
            return;
         }
         response.setContentType(TEXT_HTML_UTF8.toString());
         StringBuilder builder = new StringBuilder();
         Object timestamp = model.get("timestamp");
         Object message = model.get("message");
         Object trace = model.get("trace");
         if (response.getContentType() == null) {
            response.setContentType(getContentType());
         }
         builder.append("<html><body><h1>Whitelabel Error Page</h1>").append(
               "<p>This application has no explicit mapping for /error, so you are seeing this as a fallback.</p>")
               .append("<div id='created'>").append(timestamp).append("</div>")
               .append("<div>There was an unexpected error (type=").append(htmlEscape(model.get("error")))
               .append(", status=").append(htmlEscape(model.get("status"))).append(").</div>");
         if (message != null) {
            builder.append("<div>").append(htmlEscape(message)).append("</div>");
         }
         if (trace != null) {
            builder.append("<div style='white-space:pre-wrap;'>").append(htmlEscape(trace)).append("</div>");
         }
         builder.append("</body></html>");
         response.getWriter().append(builder.toString());
      }

      private String htmlEscape(Object input) {
         return (input != null) ? HtmlUtils.htmlEscape(input.toString()) : null;
      }

      private String getMessage(Map<String, ?> model) {
         Object path = model.get("path");
         String message = "Cannot render error page for request [" + path + "]";
         if (model.get("message") != null) {
            message += " and exception [" + model.get("message") + "]";
         }
         message += " as the response has already been committed.";
         message += " As a result, the response may have the wrong status code.";
         return message;
      }

      @Override
      public String getContentType() {
         return "text/html";
      }
   }
    ……
}

2.BasicErrorController

处理默认 /error 路径的请求,可以通过配置server.error.path或者error.path修改。

页面响应 new ModelAndView("error", model);

可以依据内容协商 响应白页(浏览器)、json(客户端)。

内容协商参考博文:blog.csdn.net/A_art_xiang…

@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {

   private final ErrorProperties errorProperties;

   /**
    * Create a new {@link BasicErrorController} instance.
    * @param errorAttributes the error attributes
    * @param errorProperties configuration properties
    */
   public BasicErrorController(ErrorAttributes errorAttributes, ErrorProperties errorProperties) {
      this(errorAttributes, errorProperties, Collections.emptyList());
   }

   /**
    * Create a new {@link BasicErrorController} instance.
    * @param errorAttributes the error attributes
    * @param errorProperties configuration properties
    * @param errorViewResolvers error view resolvers
    */
   public BasicErrorController(ErrorAttributes errorAttributes, ErrorProperties errorProperties,
         List<ErrorViewResolver> errorViewResolvers) {
      super(errorAttributes, errorViewResolvers);
      Assert.notNull(errorProperties, "ErrorProperties must not be null");
      this.errorProperties = errorProperties;
   }

   @Override
   public String getErrorPath() {
      return null;
   }
    // 写出html,寻找error视图
   @RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
   public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
      HttpStatus status = getStatus(request);
      Map<String, Object> model = Collections
            .unmodifiableMap(getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.TEXT_HTML)));
      response.setStatus(status.value());
      ModelAndView modelAndView = resolveErrorView(request, response, status, model);
      return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
   }
    // 写出去json数据
   @RequestMapping
   public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
      HttpStatus status = getStatus(request);
      if (status == HttpStatus.NO_CONTENT) {
         return new ResponseEntity<>(status);
      }
      Map<String, Object> body = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL));
      return new ResponseEntity<>(body, status);
   }

   @ExceptionHandler(HttpMediaTypeNotAcceptableException.class)
   public ResponseEntity<String> mediaTypeNotAcceptable(HttpServletRequest request) {
      HttpStatus status = getStatus(request);
      return ResponseEntity.status(status).build();
   }
    ……

}

3.DefaultErrorViewResolver

默认的错误视图解析器

public class DefaultErrorViewResolver implements ErrorViewResolver, Ordered {

   private static final Map<Series, String> SERIES_VIEWS;

   static {
      Map<Series, String> views = new EnumMap<>(Series.class);
      views.put(Series.CLIENT_ERROR, "4xx");
      views.put(Series.SERVER_ERROR, "5xx");
      SERIES_VIEWS = Collections.unmodifiableMap(views);
   }

   private ApplicationContext applicationContext;

   private final ResourceProperties resourceProperties;

   private final TemplateAvailabilityProviders templateAvailabilityProviders;

   private int order = Ordered.LOWEST_PRECEDENCE;

   /**
    * Create a new {@link DefaultErrorViewResolver} instance.
    * @param applicationContext the source application context
    * @param resourceProperties resource properties
    */
   public DefaultErrorViewResolver(ApplicationContext applicationContext, ResourceProperties resourceProperties) {
      Assert.notNull(applicationContext, "ApplicationContext must not be null");
      Assert.notNull(resourceProperties, "ResourceProperties must not be null");
      this.applicationContext = applicationContext;
      this.resourceProperties = resourceProperties;
      this.templateAvailabilityProviders = new TemplateAvailabilityProviders(applicationContext);
   }

   DefaultErrorViewResolver(ApplicationContext applicationContext, ResourceProperties resourceProperties,
         TemplateAvailabilityProviders templateAvailabilityProviders) {
      Assert.notNull(applicationContext, "ApplicationContext must not be null");
      Assert.notNull(resourceProperties, "ResourceProperties must not be null");
      this.applicationContext = applicationContext;
      this.resourceProperties = resourceProperties;
      this.templateAvailabilityProviders = templateAvailabilityProviders;
   }

    // 解析得到视图对象
   @Override
   public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
      ModelAndView modelAndView = resolve(String.valueOf(status.value()), model);
      if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
         modelAndView = resolve(SERIES_VIEWS.get(status.series()), model); //获取跳转的页面,以http状态码命名的html页面。
      }
      return modelAndView;
   }

   private ModelAndView resolve(String viewName, Map<String, Object> model) {
      String errorViewName = "error/" + viewName; // 添加一个/errpr/+视图名
      TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName,
            this.applicationContext);
      if (provider != null) {
         return new ModelAndView(errorViewName, model);
      }
      return resolveResource(errorViewName, model);
   }

   private ModelAndView resolveResource(String viewName, Map<String, Object> model) {
      for (String location : this.resourceProperties.getStaticLocations()) {
         try {
            Resource resource = this.applicationContext.getResource(location);
            resource = resource.createRelative(viewName + ".html");
            if (resource.exists()) {
               return new ModelAndView(new HtmlResourceView(resource), model);
            }
         }
         catch (Exception ex) {
         }
      }
      return null;
   }

   @Override
   public int getOrder() {
      return this.order;
   }

   public void setOrder(int order) {
      this.order = order;
   }

   /**
    * {@link View} backed by an HTML resource.
    */
   private static class HtmlResourceView implements View {

      private Resource resource;

      HtmlResourceView(Resource resource) {
         this.resource = resource;
      }

      @Override
      public String getContentType() {
         return MediaType.TEXT_HTML_VALUE;
      }

      @Override
      public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)
            throws Exception {
         response.setContentType(getContentType());
         FileCopyUtils.copy(this.resource.getInputStream(), response.getOutputStream());
      }
   }
}

4.DefaultErrorAttributes

DefaultErrorAttributes:定义错误页面中可以包含哪些数据。

@Order(Ordered.HIGHEST_PRECEDENCE)
public class DefaultErrorAttributes implements ErrorAttributes, HandlerExceptionResolver, Ordered {

   private static final String ERROR_ATTRIBUTE = DefaultErrorAttributes.class.getName() + ".ERROR";

   private final Boolean includeException;

   /**
    * Create a new {@link DefaultErrorAttributes} instance.
    */
   public DefaultErrorAttributes() {
      this.includeException = null;
   }

   /**
    * Create a new {@link DefaultErrorAttributes} instance.
    * @param includeException whether to include the "exception" attribute
    * @deprecated since 2.3.0 in favor of
    * {@link ErrorAttributeOptions#including(Include...)}
    */
   @Deprecated
   public DefaultErrorAttributes(boolean includeException) {
      this.includeException = includeException;
   }

   @Override
   public int getOrder() {
      return Ordered.HIGHEST_PRECEDENCE;
   }
    // 返回ModelAndview,一个最终跳转的页面地址
   @Override
   public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler,
         Exception ex) {
      // 保存错误的属性
      storeErrorAttributes(request, ex);
      return null;
   }

   private void storeErrorAttributes(HttpServletRequest request, Exception ex) {
      request.setAttribute(ERROR_ATTRIBUTE, ex);
   }
   // 可以保存的属性 
   @Override
   public Map<String, Object> getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) {
      Map<String, Object> errorAttributes = getErrorAttributes(webRequest, options.isIncluded(Include.STACK_TRACE));
      if (Boolean.TRUE.equals(this.includeException)) {
         options = options.including(Include.EXCEPTION);
      }
      if (!options.isIncluded(Include.EXCEPTION)) {
         errorAttributes.remove("exception");
      }
      if (!options.isIncluded(Include.STACK_TRACE)) {
         errorAttributes.remove("trace");
      }
      if (!options.isIncluded(Include.MESSAGE) && errorAttributes.get("message") != null) {
         errorAttributes.put("message", "");
      }
      if (!options.isIncluded(Include.BINDING_ERRORS)) {
         errorAttributes.remove("errors");
      }
      return errorAttributes;
   }
    // 属性
   @Override
   @Deprecated
   public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
      Map<String, Object> errorAttributes = new LinkedHashMap<>();
      errorAttributes.put("timestamp", new Date());
      addStatus(errorAttributes, webRequest);
      addErrorDetails(errorAttributes, webRequest, includeStackTrace);
      addPath(errorAttributes, webRequest);
      return errorAttributes;
   }
    // 属性
   private void addStatus(Map<String, Object> errorAttributes, RequestAttributes requestAttributes) {
      Integer status = getAttribute(requestAttributes, RequestDispatcher.ERROR_STATUS_CODE);
      if (status == null) {
         errorAttributes.put("status", 999);
         errorAttributes.put("error", "None");
         return;
      }
      errorAttributes.put("status", status);
      try {
         errorAttributes.put("error", HttpStatus.valueOf(status).getReasonPhrase());
      }
      catch (Exception ex) {
         // Unable to obtain a reason
         errorAttributes.put("error", "Http Status " + status);
      }
   }
    // 异常信息
   private void addErrorDetails(Map<String, Object> errorAttributes, WebRequest webRequest,
         boolean includeStackTrace) {
      Throwable error = getError(webRequest);
      if (error != null) {
         while (error instanceof ServletException && error.getCause() != null) {
            error = error.getCause();
         }
         errorAttributes.put("exception", error.getClass().getName());
         if (includeStackTrace) {
            addStackTrace(errorAttributes, error);
         }
      }
      addErrorMessage(errorAttributes, webRequest, error);
   }

   private void addErrorMessage(Map<String, Object> errorAttributes, WebRequest webRequest, Throwable error) {
      BindingResult result = extractBindingResult(error);
      if (result == null) {
         addExceptionErrorMessage(errorAttributes, webRequest, error);
      }
      else {
         addBindingResultErrorMessage(errorAttributes, result);
      }
   }

   private void addExceptionErrorMessage(Map<String, Object> errorAttributes, WebRequest webRequest, Throwable error) {
      Object message = getAttribute(webRequest, RequestDispatcher.ERROR_MESSAGE);
      if (StringUtils.isEmpty(message) && error != null) {
         message = error.getMessage();
      }
      if (StringUtils.isEmpty(message)) {
         message = "No message available";
      }
      errorAttributes.put("message", message);
   }

   private void addBindingResultErrorMessage(Map<String, Object> errorAttributes, BindingResult result) {
      errorAttributes.put("message", "Validation failed for object='" + result.getObjectName() + "'. "
            + "Error count: " + result.getErrorCount());
      errorAttributes.put("errors", result.getAllErrors());
   }

   private BindingResult extractBindingResult(Throwable error) {
      if (error instanceof BindingResult) {
         return (BindingResult) error;
      }
      if (error instanceof MethodArgumentNotValidException) {
         return ((MethodArgumentNotValidException) error).getBindingResult();
      }
      return null;
   }

   private void addStackTrace(Map<String, Object> errorAttributes, Throwable error) {
      StringWriter stackTrace = new StringWriter();
      error.printStackTrace(new PrintWriter(stackTrace));
      stackTrace.flush();
      errorAttributes.put("trace", stackTrace.toString());
   }
    // 错误路径
   private void addPath(Map<String, Object> errorAttributes, RequestAttributes requestAttributes) {
      String path = getAttribute(requestAttributes, RequestDispatcher.ERROR_REQUEST_URI);
      if (path != null) {
         errorAttributes.put("path", path);
      }
   }

   @Override
   public Throwable getError(WebRequest webRequest) {
      Throwable exception = getAttribute(webRequest, ERROR_ATTRIBUTE);
      return (exception != null) ? exception : getAttribute(webRequest, RequestDispatcher.ERROR_EXCEPTION);
   }

   @SuppressWarnings("unchecked")
   private <T> T getAttribute(RequestAttributes requestAttributes, String name) {
      return (T) requestAttributes.getAttribute(name, RequestAttributes.SCOPE_REQUEST);
   }

}

5.总结

错误 -> /error -> BasicErrorController -> 按照bean名字查找错误view -> DefaultErrorViewResolver以http状态作为视图页地址找到视图(404/5xx) -> 如果是浏览器就响应白页,postman就响应json

四、默认异常处理流程

1.springmvc处理逻辑参考博文

springBoot-springMVC请求处理原理_私人博客,有需要请联系17854238061(vx同号)-CSDN博客

2.DispatcherServlet的doDispatch方法

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
   HttpServletRequest processedRequest = request;
   HandlerExecutionChain mappedHandler = null;
   boolean multipartRequestParsed = false;

   WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

   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) {
            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 (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
               return;
            }
         }

         if (!mappedHandler.applyPreHandle(processedRequest, response)) {
            return;
         }
         // 执行目标方法,目标方法有任何异常都会被catch住
         // Actually invoke the handler.
         mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

         if (asyncManager.isConcurrentHandlingStarted()) {
            return;
         }

         applyDefaultViewName(processedRequest, mv);
         mappedHandler.applyPostHandle(processedRequest, response, mv);
      }
      // 执行目标方法,目标方法运行期间有任何异常都会被catch、而且标志当前请求结束;并且用 dispatchException封装。
      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);
      }
      // 进入视图解析流程(是否出现异常都会执行),如果出现异常,mv就会是null
      // 默认最终会抛出一个异常,被下面捕获(详见4、5)
      processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
   }
    // 异常被捕获,但是未处理,又抛出异常。
   catch (Exception ex) {
      triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
   }
   catch (Throwable err) {
      triggerAfterCompletion(processedRequest, response, mappedHandler,
            new NestedServletException("Handler processing failed", err));
   }
   finally {
      if (asyncManager.isConcurrentHandlingStarted()) {
         // Instead of postHandle and afterCompletion
         if (mappedHandler != null) {
            mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
         }
      }
      else {
         // Clean up any resources used by a multipart request.
         if (multipartRequestParsed) {
            cleanupMultipart(processedRequest);
         }
      }
   }
}

3.视图解析流程

private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
      @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
      @Nullable Exception exception) throws Exception {

   boolean errorView = false;
   // 如果有异常,会进入异常处理
   if (exception != null) {
      if (exception instanceof ModelAndViewDefiningException) {
         logger.debug("ModelAndViewDefiningException encountered", exception);
         mv = ((ModelAndViewDefiningException) exception).getModelAndView();
      }
      else { //进入
         Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
         // 处理handler的异常。mv会被重新定义
         mv = processHandlerException(request, response, handler, exception);
         errorView = (mv != null);
      }
   }

   // Did the handler return a view to render?
   if (mv != null && !mv.wasCleared()) {
      render(mv, request, response);
      if (errorView) {
         WebUtils.clearErrorRequestAttributes(request);
      }
   }
   else {
      if (logger.isTraceEnabled()) {
         logger.trace("No view rendering, null ModelAndView returned.");
      }
   }

   if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
      // Concurrent handling started during a forward
      return;
   }

   if (mappedHandler != null) {
      // Exception (if any) is already handled..
      mappedHandler.triggerAfterCompletion(request, response, null);
   }
}

4.处理handler发生的异常

处理handler发生的异常,处理完成返回ModelAndView

@Nullable
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
      @Nullable Object handler, Exception ex) throws Exception {
   // 移出request中的一些属性
   // Success and error responses may use different content types
   request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);

   // Check registered HandlerExceptionResolvers...
   ModelAndView exMv = null;
    // 遍历所有的 handlerExceptionResolvers,看谁能处理当前异常【HandlerExceptionResolver是一个处理器异常解析器】
   if (this.handlerExceptionResolvers != null) {
      for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
         // 调用异常解析器的resolveException方法,默认没有任何人能处理异常,所以异常会被抛出
         exMv = resolver.resolveException(request, response, handler, ex);
         if (exMv != null) {
            break;
         }
      }
   }
   if (exMv != null) {
      if (exMv.isEmpty()) {
         request.setAttribute(EXCEPTION_ATTRIBUTE, ex);
         return null;
      }
      // We might still need view name translation for a plain error model...
      if (!exMv.hasView()) {
         String defaultViewName = getDefaultViewName(request);
         if (defaultViewName != null) {
            exMv.setViewName(defaultViewName);
         }
      }
      if (logger.isTraceEnabled()) {
         logger.trace("Using resolved error view: " + exMv, ex);
      }
      else if (logger.isDebugEnabled()) {
         logger.debug("Using resolved error view: " + exMv);
      }
      WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
      return exMv;
   }
    // 默认没有任何人能处理异常,所以异常会被抛出
   throw ex;
}

5.系统默认的异常解析器

(1)DefaultErrorAttributes先来处理异常。把异常信息保存到request域,并且返回null

// org.springframework.boot.web.servlet.error.DefaultErrorAttributes#resolveException
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler,
      Exception ex) {
    // 保存错误属性
   storeErrorAttributes(request, ex);
   return null;
}

 (2)HandlerExceptionResolverComposite里面有三个resolver,会遍历执行

// org.springframework.web.servlet.handler.HandlerExceptionResolverComposite#resolveException
@Override
@Nullable
public ModelAndView resolveException(
      HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {

   if (this.resolvers != null) {
      for (HandlerExceptionResolver handlerExceptionResolver : this.resolvers) {
         ModelAndView mav = handlerExceptionResolver.resolveException(request, response, handler, ex);
         if (mav != null) {
            return mav;
         }
      }
   }
   return null;
}

(3)其他三个解析器会走父类

// org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver#resolveException
@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 debug message when warn logger is not enabled.
         if (logger.isDebugEnabled() && (this.warnLogger == null || !this.warnLogger.isWarnEnabled())) {
            logger.debug("Resolved [" + ex + "]" + (result.isEmpty() ? "" : " to " + result));
         }
         // Explicitly configured warn logger in logException method.
         logException(ex, request);
      }
      return result;
   }
   else {
      return null;
   }
}

6.如果没有任何处理器能处理异常

(1)最终底层就会转发到 /error 请求,/error请求会被底层的BasicErrorController处理(详看上面)。

(2)解析错误视图;遍历所有的 ErrorViewResolver 看谁能解析。

(3)默认的 DefaultErrorViewResolver ,作用是把响应状态码作为错误页的地址,error/500.html

(4)模板引擎最终响应这个页面 error/500.html

五、自定义错误处理逻辑

1.自定义错误页面

详情见上面。

2.@ControllerAdvice+@ExceptionHandler处理全局异常(推荐)

@ControllerAdvice+@ExceptionHandler处理全局异常;底层是 ExceptionHandlerExceptionResolver 支持的

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

/**
* 处理整个web controller的异常
*/
@Slf4j
@ControllerAdvice
public class GlobalExceptionHandler {

    @ResponseBody // 可以不加,返回一个视图
    @ExceptionHandler({ArithmeticException.class,NullPointerException.class})  //指定处理的异常
    public String handleArithException(Exception e){

        log.error("异常是:{}",e);
        return "exception handle success";
    }
}

3.@ResponseStatus+自定义异常

用处:发生该异常会响应指定的错误码,可以定义错误码对应的错误页面。

底层是 ResponseStatusExceptionResolver ,把responsestatus注解的信息底层调用 response.sendError(statusCode, resolvedReason);相当于给tomcat发送的/error。

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

// 返回一个状态码
@ResponseStatus(value= HttpStatus.FORBIDDEN,reason = "我的自定义异常")
public class MyException extends RuntimeException {

    public MyException(){}
    public MyException(String message){
        super(message);
    }
}

4.Spring底层的异常

Spring底层封装了一些异常,这些异常会有默认的异常处理逻辑。

如:

参数类型转换异常;DefaultHandlerExceptionResolver,也是会调用 response.sendError(statusCode, resolvedReason);相当于给tomcat发送的/error。

5.自定义异常解析器

import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Order(value= Ordered.HIGHEST_PRECEDENCE)  //优先级,数字越小优先级越高
@Component
public class MyHandlerExceptionResolver implements HandlerExceptionResolver {
    @Override
    public ModelAndView resolveException(HttpServletRequest request,
                                         HttpServletResponse response,
                                         Object handler, Exception ex) {

        try {
            // 写出数据
            response.sendError(511,"我喜欢的错误");
        } catch (IOException e) {
            e.printStackTrace();
        }
        return new ModelAndView();
    }
}