SpringBoot2.1.4中的错误处理机制

目录
  • SpringBoot 2.1.4 错误处理机制
    • SpringBoot错误机制原理
  • SpringBoot 2.1.3 错误处理机制
    • 引用的问题做个标记
    • 错误处理机制

SpringBoot 2.1.4 错误处理机制

springboot的自动配置中帮我们配置了相关的错误处理组件,例如访问一个不存在的页面,就会出现下面的错误页面,上面也会显示相应的信息

在Postman软件中模拟移动端访问,会获取如下响应的json数据:

可以发现springboot的错误处理机制很好的适应了不同客户端访问,浏览器返回页面,移动端返回json,那这背后springboot是如何处理的,显示的页面我想自己设计,或者返回的这些信息我们自己能够定制吗?

SpringBoot错误机制原理

springboot版本:2.1.4.RELEASE

1、默认错误页面生成机制

当我们在访问一个不存在的路径时,会出现上面的错误页面,这个页面不是我们自己创建的,而是由springboot帮我们生成的,那下面我们首先弄清楚这个默认的错误页面(Whitelabel Error Page)是怎么生成的。

1.1 springboot关于error的自动配置

package org.springframework.boot.autoconfigure.web.servlet.error包下有如下的类:

  • BasicErrorController、AbstractErrorController:错误请求控制器
  • DefaultErrorViewResolver:错误视图解析器
  • ErrorMvcAutoConfiguration:error的自动配置类

ErrorMvcAutoConfiguration

在这个配置类中注册了一些组件:

@Bean
@ConditionalOnMissingBean(
    value = {ErrorAttributes.class},
    search = SearchStrategy.CURRENT
)
// 关于error错误信息的相关类
public DefaultErrorAttributes errorAttributes() {
    return new DefaultErrorAttributes(this.serverProperties.getError().isIncludeException());
}
@Bean
@ConditionalOnMissingBean(
    value = {ErrorController.class},
    search = SearchStrategy.CURRENT
)
// 处理错误请求的控制器
public BasicErrorController basicErrorController(ErrorAttributes errorAttributes) {
    return new BasicErrorController(errorAttributes, this.serverProperties.getError(), this.errorViewResolvers);
}
@Bean
// 错误页面定制器
public ErrorMvcAutoConfiguration.ErrorPageCustomizer errorPageCustomizer() {
    return new ErrorMvcAutoConfiguration.ErrorPageCustomizer(this.serverProperties, this.dispatcherServletPath);
}

第一步:ErrorPageCustomizer

private static class ErrorPageCustomizer implements ErrorPageRegistrar, Ordered {
        private final ServerProperties properties;
        private final DispatcherServletPath dispatcherServletPath;
        protected ErrorPageCustomizer(ServerProperties properties, DispatcherServletPath dispatcherServletPath) {
            this.properties = properties;
            this.dispatcherServletPath = dispatcherServletPath;
        }
        public void registerErrorPages(ErrorPageRegistry errorPageRegistry) {
       		 // getPath()获取到一个路径“/error”
            ErrorPage errorPage = new ErrorPage(this.dispatcherServletPath.getRelativePath(this.properties.getError().getPath()));
            // 关键点:这里讲将/error的errorPage注册到了servlet,在发生异常时就会转发到/error
            errorPageRegistry.addErrorPages(new ErrorPage[]{errorPage});
        }
        public int getOrder() {
            return 0;
        }
    }

注意上面的注释,这里是为什么发生错误就会发起/error,很多博客都未说明,当然这里没有讨论其内部原理。

第二步:BasicErrorController

在错误发生后,发起 “/error” 请求,那这个 “/error” 就会由上面已经注册的BasicErrorController 接收处理。

@Controller // 表明是个控制器
@RequestMapping({"${server.error.path:${error.path:/error}}"}) // 映射的路径:/error
public class BasicErrorController extends AbstractErrorController {
    private final ErrorProperties errorProperties;
    public BasicErrorController(ErrorAttributes errorAttributes, ErrorProperties errorProperties) {
        this(errorAttributes, errorProperties, Collections.emptyList());
    }
    public BasicErrorController(ErrorAttributes errorAttributes, ErrorProperties errorProperties, List<ErrorViewResolver> errorViewResolvers) {
        super(errorAttributes, errorViewResolvers);
        Assert.notNull(errorProperties, "ErrorProperties must not be null");
        this.errorProperties = errorProperties;
    }
    public String getErrorPath() {
        return this.errorProperties.getPath();
    }
	// 处理浏览器的请求
    @RequestMapping(
        produces = {"text/html"}
    )
    public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
        HttpStatus status = this.getStatus(request);
        Map<String, Object> model = Collections.unmodifiableMap(this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.TEXT_HTML)));
        response.setStatus(status.value());
        ModelAndView modelAndView = this.resolveErrorView(request, response, status, model);
        return modelAndView != null ? modelAndView : new ModelAndView("error", model);
    }

	// 处理移动端的请求
    @RequestMapping
    public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
        Map<String, Object> body = this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.ALL));
        HttpStatus status = this.getStatus(request);
        return new ResponseEntity(body, status);
    }
    protected boolean isIncludeStackTrace(HttpServletRequest request, MediaType produces) {
        IncludeStacktrace include = this.getErrorProperties().getIncludeStacktrace();
        if (include == IncludeStacktrace.ALWAYS) {
            return true;
        } else {
            return include == IncludeStacktrace.ON_TRACE_PARAM ? this.getTraceParameter(request) : false;
        }
    }
    protected ErrorProperties getErrorProperties() {
        return this.errorProperties;
    }
}

这里可以解决一个疑惑,springboot怎么区分是浏览器还是移动端的,主要看这个方法的注解 produces={“text/html”} ,表示响应的数据是以html形式返回,这样当浏览器访问时就会调用这个方法

@RequestMapping(produces = {"text/html"})
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response){
 ...

客户端访问时就会调用下面的error方法。

@RequestMapping
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {

下面再来具体分析默认错误页面如何生成,还是来看到errorHTML方法:

public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
  // 获取错误状态码,封装到HttpStatus里面
        HttpStatus status = this.getStatus(request);
        // 获取错误信息,以map形式返回,这个后面我们具体来看,到底我们能获取到哪些数据
        Map<String, Object> model = Collections.unmodifiableMap(this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.TEXT_HTML)));
        // 设置响应体中状态码
        response.setStatus(status.value());
        // 关键点:这里就是在创建视图对象
        ModelAndView modelAndView = this.resolveErrorView(request, response, status, model);
        return modelAndView != null ? modelAndView : new ModelAndView("error", model);
    }

下面来看这个resolveErrorView方法,这个方法是父类AbstractErrorController 中的:

protected ModelAndView resolveErrorView(HttpServletRequest request, HttpServletResponse response, HttpStatus status, Map<String, Object> model) {
 // errorViewResolvers是一个list,存放ErrorViewResolver对象
    Iterator var5 = this.errorViewResolvers.iterator();
    ModelAndView modelAndView;
    // 遍历集合
    do {
        if (!var5.hasNext()) {
            return null;
        }
        ErrorViewResolver resolver = (ErrorViewResolver)var5.next();
        // 关键点:解析器对象进行视图解析
        modelAndView = resolver.resolveErrorView(request, status, model);
    } while(modelAndView == null);
    return modelAndView;
}

这里的resolveErrorView方法属于DefaultErrorViewResolver:

 public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
   // 调用下面的方法解析视图,传入参数为错误状态码,错误信息的map
        ModelAndView modelAndView = this.resolve(String.valueOf(status.value()), model);
        if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
            modelAndView = this.resolve((String)SERIES_VIEWS.get(status.series()), model);
        }
        return modelAndView;
    }
    private ModelAndView resolve(String viewName, Map<String, Object> model) {
     // 定义视图名,这里我们可以确定视图名:error/错误码,例如:error/404,
        String errorViewName = "error/" + viewName;
        // 这里结合上面的errorViewName,其实就是在template目录下的error目录进行查找
        // 我们默认情况下是没有error目录,这里的provide最终值为null,代码较多就不一一展示,有兴趣的可以跟下去
        TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName, this.applicationContext);
        // 根据判定,这里会接着调用下面的resolveResource方法
        return provider != null ? new ModelAndView(errorViewName, model) : this.resolveResource(errorViewName, model);
    }
    private ModelAndView resolveResource(String viewName, Map<String, Object> model) {
     //  getStaticLocations()获取的是静态资源路径:"classpath:/META-INF/resources/", "classpath:/resources/","classpath:/static/", "classpath:/public/"
        String[] var3 = this.resourceProperties.getStaticLocations();
        int var4 = var3.length;
  // 遍历上面的4个静态资源路径
        for(int var5 = 0; var5 < var4; ++var5) {
            String location = var3[var5];
            try {
                Resource resource = this.applicationContext.getResource(location);
                // 创建resource对象,例如error/404.html
                resource = resource.createRelative(viewName + ".html");
                // 查找在对应静态资源目录下是否有上面的这个资源对象,有就创建视图对象
                if (resource.exists()) {
                    return new ModelAndView(new DefaultErrorViewResolver.HtmlResourceView(resource), model);
                }
            } catch (Exception var8) {
                ;
            }
        }
  // 都没找到就返回null,默认情况下是不存在error目录的,所以这里最终返回null
        return null;
    }

当resolveResource方法执行完返回null,resolve方法也就返回null,在回到resolveErrorView

 public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
   // 调用下面的方法解析视图,传入参数为错误状态码,错误信息的map
        ModelAndView modelAndView = this.resolve(String.valueOf(status.value()), model);
        // 上面分析得到modelAndView的值为null,下面的if中SERIES_VIEWS.containsKey(status.series())是在判断错误码的首位是否为1,2,3,4,5,这个大家下去可以跟一下
        if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
            modelAndView = this.resolve((String)SERIES_VIEWS.get(status.series()), model);
        }
  // if里面的resolve方法分析跟上面一样,默认情况下是没有4xx.html/5xx.html页面文件的,所以最终这里返回null
        return modelAndView;
    }

这个resolveErrorView方法执行完后,我们就可以回到最开始处理 “/error” 请求的errorHtml方法了

@RequestMapping(produces = {"text/html"} )
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
    HttpStatus status = this.getStatus(request);
    Map<String, Object> model = Collections.unmodifiableMap(this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.TEXT_HTML)));
    response.setStatus(status.value());
    ModelAndView modelAndView = this.resolveErrorView(request, response, status, model);
    // modelAnView根据上面的分析其值为null
    return modelAndView != null ? modelAndView : new ModelAndView("error", model);
}

当modelAndView为null时,将会执行'new ModelAndView(“error”, model),那这个“error”又是什么呢?看下面WhitelabelErrorViewConfiguration 里面有个组件其 name就是error,这个组件是StaticView,就是一个View,里面的视图渲染方法render中的内容就是最开始我们看到的那个错误页面的内容。

@Conditional({ErrorMvcAutoConfiguration.ErrorTemplateMissingCondition.class})
protected static class WhitelabelErrorViewConfiguration {
    private final ErrorMvcAutoConfiguration.StaticView defaultErrorView = new ErrorMvcAutoConfiguration.StaticView();
    protected WhitelabelErrorViewConfiguration() {
    }
    @Bean(name = {"error"})
    @ConditionalOnMissingBean(name = {"error"})
    public View defaultErrorView() {
        return this.defaultErrorView;
    }
 ...
}
private static class StaticView implements View {
    private static final Log logger = LogFactory.getLog(ErrorMvcAutoConfiguration.StaticView.class);
    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 {
            StringBuilder builder = new StringBuilder();
            Date timestamp = (Date)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());
        }
    }
    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 = message + " and exception [" + model.get("message") + "]";
        }
        message = message + " as the response has already been committed.";
        message = message + " As a result, the response may have the wrong status code.";
        return message;
    }
    public String getContentType() {
        return "text/html";
    }
}

所以,整个大致的过程到此结束了,默认情况下错误请求处理完成后就返回的这个StaticView定义的页面,下图做个基本的梳理。后续再来做自定义错误页面、自定义错误数据的原理分析。

SpringBoot 2.1.3 错误处理机制

引用的问题做个标记

以前的引用好像在新版本中无法引用了

错误处理机制

其他的程序的类的声明直接用IDEA的提示来用就可以了。

如果还是有错误的话,就进入到lib中看看引用的类的方法就可以了

import org.springframework.boot.autoconfigration.web.DefaultErrorAttributes;//这是以前的
import org.springframework.boot.web.servlet.error.DefaultErrorAttributes;//这是现在的

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • springboot 错误处理小结

    在 java web开发过程中,难免会有一些系统异常或人为产生一些异常.在 RESTful springboot 项目中如何优雅的处理? 分析:在RESTful 风格的springboot 项目中,返回的都是 body 对象,所以定义一个结果基类,其中包含 status,message,data(请求方法的返回结果),是比较合适的. 如果定义多个异常类进行处理,会比较麻烦.比如StudentNotExistsException.StudentExistsException...等,并且不能指定错

  • SpringBoot 错误处理机制与自定义错误处理实现详解

    [1]SpringBoot的默认错误处理 ① 浏览器访问 请求头如下: ② 使用"PostMan"访问 { "timestamp": 1529479254647, "status": 404, "error": "Not Found", "message": "No message available", "path": "/aaa1&q

  • Springboot异常错误处理解决方案详解

    1.在有模板引擎的情况下: springboot会默认找 templates/error/错误状态码.html,所以我们要定制化错误页面就可以到templates/error下创建一个[对应错误状态码.html]html文件,当发生此状态码的错误springboot就会来到对应的页面. 同时如果我们想让400-499之间的错误都去同一个错误页面,那我们可以在templates/error下创建一个4xx.html.同理500-599的错误可以用5xx.html. 注意:springboot会优先

  • Springboot错误处理机制实现原理解析

    1.默认的错误机制 默认效果 ①在浏览器中访问不存在的请求时,springboot默认返回一个空白页面 浏览器的请求头 ②客户端访问时,返回json数据 { "timestamp": "2020-03-24T02:49:56.572+0000", "status": 404, "error": "Not Found", "message": "No message availa

  • Springboot实现自定义错误页面的方法(错误处理机制)

    一般我们在做项目的时候,错误机制是必备的常识,基本每个项目都会做错误处理,不可能项目一报错直接跳到原始报错页面,本篇博客主要针对springboot默认的处理机制,以及自定义错误页面处理进行讲解,需要的朋友们下面随着小编来一起学习学习吧! 默认效果示例 springboot他是有自己默认的处理机制的.在你刚创建一个springboot项目去访问一个没有的路径会发现他是会弹出来这样的信息. 而我们用postman直接接口访问,会发现他返回的不再是页面.默认响应一个json数据 这时候该有人在想,s

  • SpringBoot2.1.4中的错误处理机制

    目录 SpringBoot 2.1.4 错误处理机制 SpringBoot错误机制原理 SpringBoot 2.1.3 错误处理机制 引用的问题做个标记 错误处理机制 SpringBoot 2.1.4 错误处理机制 springboot的自动配置中帮我们配置了相关的错误处理组件,例如访问一个不存在的页面,就会出现下面的错误页面,上面也会显示相应的信息 在Postman软件中模拟移动端访问,会获取如下响应的json数据: 可以发现springboot的错误处理机制很好的适应了不同客户端访问,浏览

  • 深入分析javascript中的错误处理机制

    前面的话 错误处理对于web应用程序开发至关重要,不能提前预测到可能发生的错误,不能提前采取恢复策略,可能导致较差的用户体验.由于任何javascript错误都可能导致网页无法使用,因此作为开发人员,必须要知道何时可能出错,为什么会出错,以及会出什么错.本文将详细介绍javascript中的错误处理机制 error对象 error对象是包含错误信息的对象,是javascript的原生对象.当代码解析或运行时发生错误,javascript引擎就会自动产生并抛出一个error对象的实例,然后整个程序

  • 全面了解javascript中的错误处理机制

    前面的话 错误处理对于web应用程序开发至关重要,不能提前预测到可能发生的错误,不能提前采取恢复策略,可能导致较差的用户体验.由于任何javascript错误都可能导致网页无法使用,因此作为开发人员,必须要知道何时可能出错,为什么会出错,以及会出什么错.本文将详细介绍javascript中的错误处理机制 error对象 error对象是包含错误信息的对象,是javascript的原生对象.当代码解析或运行时发生错误,javascript引擎就会自动产生并抛出一个error对象的实例,然后整个程序

  • GO语言标准错误处理机制error用法实例

    本文实例讲述了GO语言标准错误处理机制error用法.分享给大家供大家参考.具体分析如下: 在 Golang 中,错误处理机制一般是函数返回时使用的,是对外的接口,而异常处理机制 panic-recover 一般用在函数内部. error 类型介绍 error 类型实际上是抽象了 Error() 方法的 error 接口,Golang 使用该接口进行标准的错误处理. 复制代码 代码如下: type error interface {  Error() string } 一般情况下,如果函数需要返

  • golang 语言中错误处理机制

    与其他主流语言如 Javascript.Java 和 Python 相比,Golang 的错误处理方式可能和这些你熟悉的语言有所不同.所以才有了这个想法根大家聊一聊 golang 的错误处理方式,以及实际开发中应该如何对错误进行处理.因为分享面对 Golang有一个基本的了解 developers, 所以一些简单地方就不做赘述了. 如何定义错误 在 golang 语言中,无论是在类型检查还是编译过程中,都是将错误看做值来对待,和 string 或者 integer 这些类型值并不差别.声明一个

  • PHP中的错误及其处理机制

    目录 什么是错误? Fatal Error:致命错误(脚本终止运行) Parse Error:编译时解析错误,语法错误(脚本终止运行) Warning Error:警告错误(仅给出提示信息,脚本不终止运行) Notice Error:通知错误(仅给出通知信息,脚本不终止运行) 总结 在PHP的学习过程中,我们会接触到两个概念,一个是错误,一个是异常.啥玩意?他们不是一个东西嘛?如果接触过Java.C#之类的纯面向对象语言的同学,可能对异常是没有什么问题,毕竟所有的问题都可以try...catch

  • asp.net 的错误处理机制讲解

    程序健壮性最基本要求就是程序错误的处理与捕捉,在ASP.NET中,错误的处理有和其他编程语言一样的机制,可以使用Try-Catch-Finally等方式,这一点和ASP相比具有较大的进步.而且,使用这些错误处理方法,可以大大提高程序的可读性和程序调试速度,在这几个优势结合的情况下,我们更加应该注意这一点.  关于错误的处理,我们可以参考这篇文章: Try...Catch...Finally in ASP.NET Introduction Error handling in Classic ASP

  • NodeJS处理Express中异步错误

    摘要 比起回调函数,使用 Promise 来处理异步错误要显得优雅许多. 结合 Express 内置的错误处理机制和 Promise 极大地降低产生未捕获错误(uncaught exception)的可能性. Promise 在ES6中是默认选项.如果使用 Babel 转译,它也可以与 Generators 或者 Async/Await 相结合. 本文主要阐述如何在 Express 中使用错误处理中间件(error-handling middleware)来高效处理异步错误.在 Github 上

  • 实例讲解如何在PHP的Yii框架中进行错误和异常处理

    Yii已经默认已经在CApplication上实现了异常和错误的接管,这是通过php的set_exception_handler,set_error_handler实现的.通过这两个PHP内置函数,可以对程序中未捕获的异常以及错误进行接管处理,从而提高程序的可维护性.这在大型系统是至关重要的,当发生错误时,我们希望能将相关详细信息记录,甚至是即时发送报警,从而缩短故障修复时间,提高整个系统的稳定性. 默认情况下,Yii会将异常处理分配给CApplication::handleException,

  • SpringBoot错误处理机制以及自定义异常处理详解

    上篇文章我们讲解了使用Hibernate Validation来校验数据,当校验完数据后,如果发生错误我们需要给客户返回一个错误信息,因此这节我们来讲解一下SpringBoot默认的错误处理机制以及如何自定义异常来处理请求错误. 一.SpringBoot默认的错误处理机制 我们在发送一个请求的时候,如果发生404 SpringBoot会怎么处理呢?我们来发送一个不存在的请求来验证一下看看页面结果.如下所示: 当服务器内部发生错误的时候,页面会返回什么呢? @GetMapping("/user/{

随机推荐