ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

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

2021-07-14 11:34:51  阅读:197  来源: 互联网

标签:model springboot mappedHandler request 默认 错误处理 null response append


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

1)浏览器访问

 

 

 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)浏览器访问

 

 

 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文件

 

 

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

 

 

 

 

 

 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它拦截处理

 

 这里说明下:为什么浏览器请求返回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/"

标签:model,springboot,mappedHandler,request,默认,错误处理,null,response,append
来源: https://www.cnblogs.com/johnzhao/p/15010043.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有