8.springboot默认错误处理机制以及原理

1.提供一个错误的地址  http://localhost:8087/aaaaaaaaa

1)浏览器访问

8.springboot默认错误处理机制以及原理

 

 

 2)postman调用

{
    "timestamp": "2021-07-14T02:41:21.571+00:00",
    "status": 404,
    "error": "Not Found",
    "message": "",
    "path": "/aaaaaaaaa"
}

2.提供一个异常的接口

  @GetMapping("/compare")
  public String compare(HttpServletRequest request){
    int i=10/0;
    return "index";
  }

1)浏览器访问

8.springboot默认错误处理机制以及原理

 

 

 2) postman调用

{
    "timestamp": "2021-07-14T02:42:54.914+00:00",
    "status": 500,
    "error": "Internal Server Error",
    "message": "",
    "path": "/compare"
}

3.自己提供错误页面

1)在resource下面的static目录下创建目录error,里面放置3个html文件

8.springboot默认错误处理机制以及原理

 

 

 2)继续浏览器访问2个接口

8.springboot默认错误处理机制以及原理

 

 

 8.springboot默认错误处理机制以及原理

 

 

 3)解释下5xx.html:如果没有500.html的话,系统就使用5xx.html

 

4.源码解读

1)DispatchServlet

org.springframework.web.servlet.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 {
      try {
        ModelAndView mv = null;
        Object dispatchException = null;

        try {
          processedRequest = this.checkMultipart(request);
          multipartRequestParsed = processedRequest != request;
//查询哪个处理器(controller)能处理我们的请求 mappedHandler = this.getHandler(processedRequest); if (mappedHandler == null) { this.noHandlerFound(processedRequest, response); return; } //参数处理适配器 HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler()); 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; } //真正调用目标方法 mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); if (asyncManager.isConcurrentHandlingStarted()) { return; } this.applyDefaultViewName(processedRequest, mv); mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception var20) {
//目标方法执行成异常后,并没有退出,而是捕获异常继续执行 dispatchException = var20; } catch (Throwable var21) { dispatchException = new NestedServletException("Handler dispatch failed", var21); } //最终结果处理器 this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException); } catch (Exception var22) {
//拦截器的complete方法 this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22); } catch (Throwable var23) {
//拦截器的complete方法 this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23)); } } finally { if (asyncManager.isConcurrentHandlingStarted()) { if (mappedHandler != null) { mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); } } else if (multipartRequestParsed) { this.cleanupMultipart(processedRequest); } } }

我们看最终处理器

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) {
        this.logger.debug("ModelAndViewDefiningException encountered", exception);
        mv = ((ModelAndViewDefiningException)exception).getModelAndView();
      } else {
        Object handler = mappedHandler != null ? mappedHandler.getHandler() : null;
//自定义异常处理 mv = this.processHandlerException(request, response, handler, exception); errorView = mv != null; } } if (mv != null && !mv.wasCleared()) { this.render(mv, request, response); if (errorView) { WebUtils.clearErrorRequestAttributes(request); } } else if (this.logger.isTraceEnabled()) { this.logger.trace("No view rendering, null ModelAndView returned."); } if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) { if (mappedHandler != null) { mappedHandler.triggerAfterCompletion(request, response, (Exception)null); } } }

我们debug发现我们没有自定义异常处理器,整个流程下来返回空modelAndView,这时候底层发起一个/error请求,这个请求会被BasicErrorController它拦截处理

8.springboot默认错误处理机制以及原理

 

 这里说明下:为什么浏览器请求返回html页面,而postman请求返回json.原因是contentType导致,浏览器使用默认的,而postman使用application/json.

所有上面2个方法,第一个给html用的,第二个给postman请求返回的。

 

我们继续分析html的

    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); }

1)先说下默认错误视图在哪

org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration.StaticView

这个是是我们自动装配进来的,包括BasicErrorController也是因为自动装配才生效的。

private static class StaticView implements View {
    private static final MediaType TEXT_HTML_UTF8;
    private static final Log logger;

    private StaticView() {
    }

    public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
      if (response.isCommitted()) {
        String message = this.getMessage(model);
        logger.error(message);
      } else {
        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(this.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(this.htmlEscape(model.get("error"))).append(", status=").append(this.htmlEscape(model.get("status"))).append(").</div>");
        if (message != null) {
          builder.append("<div>").append(this.htmlEscape(message)).append("</div>");
        }

        if (trace != null) {
          builder.append("<div style='white-space:pre-wrap;'>").append(this.htmlEscape(trace)).append("</div>");
        }

        builder.append("</body></html>");
        response.getWriter().append(builder.toString());
      }
    }

我们的默认html标签都是在这里组装好的。

2)继续分析原来的获取错误视图org.springframework.boot.autoconfigure.web.servlet.error.AbstractErrorController#resolveErrorView

    protected ModelAndView resolveErrorView(HttpServletRequest request, HttpServletResponse response, HttpStatus status,
            Map<String, Object> model) {
//错误视图解析器,系统默认只装配了一个,ErrorMvcAutoConfiguration在它里面装配的 for (ErrorViewResolver resolver : this.errorViewResolvers) { ModelAndView modelAndView = resolver.resolveErrorView(request, status, model);
//如果解析到了用解析到的,如果解析不到返回null,最终使用使用默认error视图 if (modelAndView != null) { return modelAndView; } } return null; }

3)查看视图解析器

    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); } return modelAndView; }

这里有个默认视图

    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);
    }

4)根据状态码解析视图

    private ModelAndView resolve(String viewName, Map<String, Object> model) {
        String errorViewName = "error/" + viewName;
        TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName,
                this.applicationContext);
        if (provider != null) {
            return new ModelAndView(errorViewName, model);
        }
//这里根据/error/404和model参数去获取视图 return resolveResource(errorViewName, model); }

5)获取视图

private ModelAndView resolveResource(String viewName, Map<String, Object> model) {
//这里定义了视图的放置位置 for (String location : this.resources.getStaticLocations()) { try { Resource resource = this.applicationContext.getResource(location);
//看看位置下有没有比如error/404.html这样的问题 resource = resource.createRelative(viewName + ".html");
//有这样的文件直接根据html返回视图 if (resource.exists()) { return new ModelAndView(new HtmlResourceView(resource), model); } } catch (Exception ex) { } } return null; }

这里的位置有

0 = "classpath:/META-INF/resources/"
1 = "classpath:/resources/"
2 = "classpath:/static/"
3 = "classpath:/public/"

上一篇:SpringBoot系列——Filter 过滤器


下一篇:Springboot处理请求流程源码分析