SpringBoot学习笔记 深入理解SpringBoot异常处理

2022-08-17 08:36:49

上篇Blog详细聊了SpringBoot的拦截器使用,上上篇Blog详细介绍了SpringBoot是如何定制整合页面模板Thymeleaf的,这些都可以看做SpringBoot对SpringMVC功能层面的一个完善,那么本篇Blog继续介绍SpringBoot如何处理前端请求的异常,自动配置是如何帮助我们定制错误页面并轻松应用。

SpringBoot默认异常处理

在日常的 Web 开发中,会经常遇到大大小小的异常,此时往往需要一个统一的异常处理机制,来保证客户端能接收较为友好的提示。

Spring Boot 默认异常处理机制

Spring Boot 同样提供了一套默认的异常处理机制,一旦程序中出现了异常,Spring Boot 会自动识别客户端的类型(浏览器客户端或机器客户端),并根据客户端的不同,以不同的形式展示异常信息:

对于浏览器客户端而言,Spring Boot 会响应一个“ whitelabel”错误视图,以 HTML 格式呈现错误信息
在这里插入图片描述
对于机器客户端(IOS、Android或者PostMan)而言,Spring Boot 将生成 JSON 响应,来展示异常消息
在这里插入图片描述

Spring Boot 异常处理自动配置原理

Spring Boot 通过配置类 ErrorMvcAutoConfiguration 对异常处理提供了自动配置:
在这里插入图片描述

该配置类向容器中注入了以下 4 个组件

  1. ErrorPageCustomizer:该组件会在在系统发生异常后,默认将请求转发到/error上。
  2. BasicErrorController:处理默认的/error请求。
  3. DefaultErrorViewResolver:默认的错误视图解析器,将异常信息解析到相应的错误视图上。
  4. DefaultErrorAttributes:用于页面上共享异常信息。

下面,我们依次对这四个组件进行详细的介绍。

1 ErrorPageCustomizer

ErrorMvcAutoConfiguration 向容器中注入了一个名为 ErrorPageCustomizer 的组件,它主要用于定制错误页面的响应规则

@BeanpublicErrorPageCustomizererrorPageCustomizer(DispatcherServletPath dispatcherServletPath){returnnewErrorPageCustomizer(this.serverProperties, dispatcherServletPath);}

ErrorPageCustomizer 通过registerErrorPages() 方法来注册错误页面的响应规则。当系统中发生异常后,ErrorPageCustomizer 组件会自动生效,并将请求转发到/error上,交给 BasicErrorController 进行处理,其源代码如下:

staticclassErrorPageCustomizerimplementsErrorPageRegistrar,Ordered{privatefinalServerProperties properties;privatefinalDispatcherServletPath dispatcherServletPath;protectedErrorPageCustomizer(ServerProperties properties,DispatcherServletPath dispatcherServletPath){this.properties= properties;this.dispatcherServletPath= dispatcherServletPath;}publicvoidregisterErrorPages(ErrorPageRegistry errorPageRegistry){ErrorPage errorPage=newErrorPage(this.dispatcherServletPath.getRelativePath(this.properties.getError().getPath()));
            errorPageRegistry.addErrorPages(newErrorPage[]{errorPage});}publicintgetOrder(){return0;}}

默认的路径是通过ServerProperties下定义的ErrorProperties定义的默认值
在这里插入图片描述
ErrorProperties定义的默认值可以看到,默认错误路径为/error
在这里插入图片描述

2 BasicErrorController

ErrorMvcAutoConfiguration 向容器中注入了一个错误控制器组件 BasicErrorController,代码如下

@Bean@ConditionalOnMissingBean(
        value={ErrorController.class},
        search=SearchStrategy.CURRENT)publicBasicErrorControllerbasicErrorController(ErrorAttributes errorAttributes,ObjectProvider<ErrorViewResolver> errorViewResolvers){returnnewBasicErrorController(errorAttributes,this.serverProperties.getError(),(List)errorViewResolvers.orderedStream().collect(Collectors.toList()));}

BasicErrorController 的定义如下

/BasicErrorController 用于处理 “/error” 请求@Controller@RequestMapping("${server.error.path:${error.path:/error}}")publicclassBasicErrorControllerextendsAbstractErrorController{....../**
     * 该方法用于处理浏览器客户端的请求发生的异常
     * 生成 html 页面来展示异常信息
     * @param request
     * @param response
     * @return
     */@RequestMapping(produces=MediaType.TEXT_HTML_VALUE)publicModelAndViewerrorHtml(HttpServletRequest request,HttpServletResponse response){//获取错误状态码HttpStatus status=getStatus(request);//getErrorAttributes 根据错误信息来封装一些 model 数据,用于页面显示Map<String,Object> model=Collections.unmodifiableMap(getErrorAttributes(request,getErrorAttributeOptions(request,MediaType.TEXT_HTML)));//为响应对象设置错误状态码
        response.setStatus(status.value());//调用 resolveErrorView() 方法,使用错误视图解析器生成 ModelAndView 对象(包含错误页面地址和页面内容)ModelAndView modelAndView=resolveErrorView(request, response, status, model);return(modelAndView!=null)? modelAndView:newModelAndView("error", model);}/**
     * 该方法用于处理机器客户端的请求发生的错误
     * 产生 JSON 格式的数据展示错误信息
     * @param request
     * @return
     */@RequestMappingpublicResponseEntity<Map<String,Object>>error(HttpServletRequest request){HttpStatus status=getStatus(request);if(status==HttpStatus.NO_CONTENT){returnnewResponseEntity<>(status);}Map<String,Object> body=getErrorAttributes(request,getErrorAttributeOptions(request,MediaType.ALL));returnnewResponseEntity<>(body, status);}......}

Spring Boot 通过 BasicErrorController 进行统一的错误处理(例如默认的/error请求)。Spring Boot 会自动识别发出请求的客户端的类型(浏览器客户端或机器客户端),并根据客户端类型,将请求分别交给errorHtmlerror 方法进行处理,当使用浏览器访问出现异常时,会进入 BasicErrorController 控制器中的 errorHtml方法进行处理,当使用安卓、IOS、Postman 等机器客户端访问出现异常时,就进入error方法处理
在这里插入图片描述
在 errorHtml() 方法中会调用父类(AbstractErrorController)的 resolveErrorView() 方法,代码如下

protectedModelAndViewresolveErrorView(HttpServletRequest request,HttpServletResponse response,HttpStatus status,Map<String,Object> model){//获取容器中的所有的错误视图解析器来处理该异常信息Iterator var5=this.errorViewResolvers.iterator();ModelAndView modelAndView;do{if(!var5.hasNext()){returnnull;}ErrorViewResolver resolver=(ErrorViewResolver)var5.next();//调用错误视图解析器的 resolveErrorView 解析到错误视图页面
            modelAndView= resolver.resolveErrorView(request, status, model);}while(modelAndView==null);return modelAndView;}

从上述源码可以看出,在响应页面的时候,会在父类的 resolveErrorView 方法中获取容器中所有的ErrorViewResolver 对象(错误视图解析器,包括 DefaultErrorViewResolver 在内),一起来解析异常信息。

3 DefaultErrorViewResolver

BasicErrorController请求会解析错误视图,默认的就是ErrorMvcAutoConfiguration 向容器中注入的默认错误视图解析器组件 DefaultErrorViewResolver,代码如下

@Bean@ConditionalOnBean({DispatcherServlet.class})@ConditionalOnMissingBean({ErrorViewResolver.class})//有自定义的错误视图解析器时不启用默认的DefaultErrorViewResolverconventionErrorViewResolver(){returnnewDefaultErrorViewResolver(this.applicationContext,this.resources);}

当发出请求的客户端为浏览器时,Spring Boot 会获取容器中所有的 ErrorViewResolver 对象(错误视图解析器),并分别调用它们的 resolveErrorView() 方法对异常信息进行解析,其中自然也包括 DefaultErrorViewResolver(默认错误信息解析器),部分代码如下

publicclassDefaultErrorViewResolverimplementsErrorViewResolver,Ordered{privatestaticfinalMap<HttpStatus.Series,String> SERIES_VIEWS;//1 根据错误状态码(例如 404、500、400 等),生成一个错误视图 error/statusstatic{Map<HttpStatus.Series,String> views=newEnumMap<>(HttpStatus.Series.class);
        views.put(Series.CLIENT_ERROR,"4xx");
        views.put(Series.SERVER_ERROR,"5xx");
        SERIES_VIEWS=Collections.unmodifiableMap(views);}......//2 尝试使用模板引擎解析 error/status 视图,即尝试从 classpath 类路径下的 templates 目录下,查找 error/status.html@OverridepublicModelAndViewresolveErrorView(HttpServletRequest request,HttpStatus status,Map<String,Object> model){//尝试以错误状态码作为错误页面名进行解析ModelAndView modelAndView=resolve(String.valueOf(status.value()), model);if(modelAndView==null&& SERIES_VIEWS.containsKey(status.series())){//尝试以 4xx 或 5xx 作为错误页面页面进行解析
            modelAndView=resolve(SERIES_VIEWS.get(status.series()), model);}return modelAndView;}//3 若模板引擎能够解析到 error/status 视图,则将视图和数据封装成 ModelAndView 返回并结束整个解析流程privateModelAndViewresolve(String viewName,Map<String,Object> model){//错误模板页面,例如 error/404、error/4xx、error/500、error/5xxString errorViewName="error/"+ viewName;//当模板引擎可以解析这些模板页面时,就用模板引擎解析TemplateAvailabilityProvider provider=this.templateAvailabilityProviders.getProvider(errorViewName,this.applicationContext);if(provider!=null){//在模板能够解析到模板页面的情况下,返回 errorViewName 指定的视图returnnewModelAndView(errorViewName, model);}//若模板引擎不能解析,则去静态资源文件夹下查找 errorViewName 对应的页面returnresolveResource(errorViewName, model);}//4 依次从各个静态资源文件夹中查找 error/status.html,若在静态文件夹中找到了该错误页面,则返回并结束整个解析流程,否则跳转到第 5 步privateModelAndViewresolveResource(String viewName,Map<String,Object> model){//遍历所有静态资源文件夹for(String location:this.resources.getStaticLocations()){try{Resource resource=this.applicationContext.getResource(location);//静态资源文件夹下的错误页面,例如error/404.html、error/4xx.html、error/500.html、error/5xx.html
                resource= resource.createRelative(viewName+".html");//若静态资源文件夹下存在以上错误页面,则直接返回if(resource.exists()){returnnewModelAndView(newDefaultErrorViewResolver.HtmlResourceView(resource), model);}}catch(Exception ex){}}returnnull;}......}

DefaultErrorViewResolver 解析异常信息的步骤如下:

  1. 根据错误状态码(例如 404、500、400 等),生成一个错误视图 error/status,例如 error/404、error/500、error/400。
  2. 尝试使用模板引擎解析 error/status 视图,即尝试从 classpath 类路径下的 templates 目录下,查找 error/status.html,例如 error/404.html、error/500.html、error/400.html。1动态精确匹配
  3. 若模板引擎能够解析到 error/status 视图,则将视图和数据封装成 ModelAndView 返回并结束整个解析流程,否则跳转到第 4 步。
  4. 依次从各个静态资源文件夹中查找 error/status.html,若在静态文件夹中找到了该错误页面,则返回并结束整个解析流程,否则跳转到第 5 步。2静态精确匹配
  5. 将错误状态码(例如 404、500、400 等)转换为 4xx 或 5xx,然后重复前 4 个步骤,若解析成功则返回并结束整个解析流程,否则跳转第 6 步。3动态模糊匹配,4静态精确匹配
  6. 处理默认的 “/error ”请求,使用 Spring Boot 默认的错误页面(Whitelabel Error Page)5默认错误匹配

按照如上流程匹配后错误页面就可以路由跳转了。

4 DefaultErrorAttributes

ErrorMvcAutoConfiguration 还向容器中注入了一个组件默认错误属性处理工具 DefaultErrorAttributes,代码如下

@Bean@ConditionalOnMissingBean(value=ErrorAttributes.class, search=SearchStrategy.CURRENT)publicDefaultErrorAttributeserrorAttributes(){returnnewDefaultErrorAttributes();}

DefaultErrorAttributes 是 Spring Boot 的默认错误属性处理工具,它可以从请求中获取异常或错误信息,并将其封装为一个 Map 对象返回,其部分代码如下

publicclassDefaultErrorAttributesimplementsErrorAttributes,HandlerExceptionResolver,Ordered{......@OverridepublicMap<String,Object>getErrorAttributes(WebRequest webRequest,ErrorAttributeOptions options){Map<String,Object> errorAttributes=getErrorAttributes(webRequest, options.isIncluded(Include.STACK_TRACE));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.remove("message");}if(!options.isIncluded(Include.BINDING_ERRORS)){
            errorAttributes.remove("errors");}return errorAttributes;}privateMap<String,Object>getErrorAttributes(WebRequest webRequest,boolean includeStackTrace){Map<String,Object> errorAttributes=newLinkedHashMap<>();
        errorAttributes.put("timestamp",newDate());addStatus(errorAttributes, webRequest);addErrorDetails(errorAttributes, webRequest, includeStackTrace);addPath(errorAttributes, webRequest);return errorAttributes;}......}

在 Spring Boot 默认的 Error 控制器(BasicErrorController)处理错误时,会调用 DefaultErrorAttributes 的 getErrorAttributes() 方法获取错误或异常信息,并封装成 model 数据(Map 对象),返回到页面或 JSON 数据中。该 model 数据主要包含以下属性:

  • timestamp:时间戳;
  • status:错误状态码
  • error:错误的提示
  • exception:导致请求处理失败的异常对象
  • message:错误/异常消息
  • trace: 错误/异常栈信息
  • path:错误/异常抛出时所请求的URL路径

所有通过 DefaultErrorAttributes 封装到 model 数据中的属性,都可以直接在页面或 JSON 中获取

SpringBoot全局异常处理

Spring Boot 已经提供了一套默认的异常处理机制,但是 Spring Boot 提供的默认异常处理机制却并不一定适合我们实际的业务场景,因此,我们通常会根据自身的需要对 Spring Boot 全局异常进行统一定制,例如定制错误页面,定制错误数据等

定制错误页面

我们可以通过以下 3 种方式定制 Spring Boot 错误页面:自定义 error.html;自定义动态错误页面;自定义静态错误页面,其实也就对应我们上边提到的错误视图解析器组件的路由匹配。我们整体使用不存在的页面请求做测试http://localhost:8080/test.html

1 自定义动态错误页面

如果 Sprng Boot 项目使用了模板引擎,当程序发生异常时,Spring Boot 的默认错误视图解析器(DefaultErrorViewResolver)就会解析模板引擎文件夹(resources/templates/)下 error 目录中的错误视图页面
在这里插入图片描述

精确匹配

我们可以根据错误状态码(例如 404、400 等等)的不同,分别创建不同的动态错误页面(例如 404.html、500.html、400.html 等等),并将它们存放在模板引擎文件夹下的 error 目录中。当发生异常时,Spring Boot 会根据其错误状态码精确匹配到对应的错误页面上

<!DOCTYPEhtml><htmllang="en"xmlns:th="http://www.thymeleaf.org"><head><metacharset="UTF-8"><title></title></head><body><h1>自定义动态错误页面 404.html</h1><p>status:<spanth:text="${status}"></span></p><p>error:<spanth:text="${error}"></span></p><p>timestamp:<spanth:text="${timestamp}"></span></p><p>message:<spanth:text="${message}"></span></p><p>path:<spanth:text="${path}"></span></p></body></html>

请求地址后返回结果如下:
在这里插入图片描述

模糊匹配

当我们没有精确匹配的时候看看会不会匹配到模糊匹配,首先把404.html页面修改为500.html,测试完再改回去。然后我们新增一个4xx.html页面

<!DOCTYPEhtml><htmllang="en"xmlns:th="http://www.thymeleaf.org"><head><
  • 作者:存在morning
  • 原文链接:https://tianmaolin.blog.csdn.net/article/details/120653196
    更新时间:2022-08-17 08:36:49