【本人秃顶程序员】Spring Boot 中关于自定义异常处理的套路! 深碍√TFBOYSˉ_ 2022-02-17 01:43 197阅读 0赞 ←←←←←←←←←←←← 快!点关注 在 Spring Boot 项目中 ,异常统一处理,可以使用 Spring 中 @ControllerAdvice 来统一处理,也可以自己来定义异常处理方案。Spring Boot 中,对异常的处理有一些默认的策略,我们分别来看。 默认情况下,Spring Boot 中的异常页面 是这样的: ![1240][] 我们从这个异常提示中,也能看出来,之所以用户看到这个页面,是因为开发者没有明确提供一个 /error 路径,如果开发者提供了 /error 路径 ,这个页面就不会展示出来,不过在 Spring Boot 中,提供 /error 路径实际上是下下策,Spring Boot 本身在处理异常时,也是当所有条件都不满足时,才会去找 /error 路径。那么我们就先来看看,在 Spring Boot 中,如何自定义 error 页面,整体上来说,可以分为两种,一种是静态页面,另一种是动态页面。 # 静态异常页面 # -------------------- 自定义静态异常页面,又分为两种,第一种 是使用 HTTP 响应码来命名页面,例如 404.html、405.html、500.html …,另一种就是直接定义一个 4xx.html,表示400-499 的状态都显示这个异常页面,5xx.html 表示 500-599 的状态显示这个异常页面。 默认是在`classpath:/static/error/`路径下定义相关页面: ![1240 1][] 此时,启动项目,如果项目抛出 500 请求错误,就会自动展示 500.html 这个页面,发生 404 就会展示 404.html 页面。如果异常展示页面既存在 5xx.html,也存在 500.html ,此时,发生500异常时,优先展示 500.html 页面。 # 动态异常页面 # -------------------- 动态的异常页面定义方式和静态的基本 一致,可以采用的页面模板有 jsp、freemarker、thymeleaf。动态异常页面,也支持 404.html 或者 4xx.html ,但是一般来说,由于动态异常页面可以直接展示异常详细信息,所以就没有必要挨个枚举错误了 ,直接定义 4xx.html(这里使用thymeleaf模板)或者 5xx.html 即可。 > 注意,动态页面模板,不需要开发者自己去定义控制器,直接定义异常页面即可 ,Spring Boot 中自带的异常处理器会自动查找到异常页面。 页面定义如下: ![1240 2][] 页面内容如下: <!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>5xx</h1> <table border="1"> <tr> <td>path</td> <td th:text="${path}"></td> </tr> <tr> <td>error</td> <td th:text="${error}"></td> </tr> <tr> <td>message</td> <td th:text="${message}"></td> </tr> <tr> <td>timestamp</td> <td th:text="${timestamp}"></td> </tr> <tr> <td>status</td> <td th:text="${status}"></td> </tr> </table> </body> </html> 默认情况下,完整的异常信息就是这5条,展示 效果如下 : ![1240 3][] 如果动态页面和静态页面同时定义了异常处理页面,例如 `classpath:/static/error/404.html`和`classpath:/templates/error/404.html`同时存在时,默认使用动态页面。即完整的错误页面查找方式应该是这样: 发生了500错误–>查找动态 500.html 页面–>查找静态 500.html --> 查找动态 5xx.html–>查找静态 5xx.html。 # 自定义异常数据 # -------------------- 默认情况下,在Spring Boot 中,所有的异常数据其实就是上文所展示出来的5条数据,这5条数据定义在 `org.springframework.boot.web.reactive.error.DefaultErrorAttributes`类中,具体定义在`getErrorAttributes`方法中 : @Override public Map<String, Object> getErrorAttributes(ServerRequest request, Boolean includeStackTrace) { Map<String, Object> errorAttributes = new LinkedHashMap<>(); errorAttributes.put("timestamp", new Date()); errorAttributes.put("path", request.path()); Throwable error = getError(request); HttpStatus errorStatus = determineHttpStatus(error); errorAttributes.put("status", errorStatus.value()); errorAttributes.put("error", errorStatus.getReasonPhrase()); errorAttributes.put("message", determineMessage(error)); handleException(errorAttributes, determineException(error), includeStackTrace); return errorAttributes; } DefaultErrorAttributes 类本身则是在org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration 异常自动配置类中定义的,如果开发者没有自己提供一个 ErrorAttributes 的实例的话,那么 Spring Boot 将自动提供一个ErrorAttributes 的实例,也就是 DefaultErrorAttributes 。 基于此 ,开发者自定义 ErrorAttributes 有两种方式 : 1. 直接实现 ErrorAttributes 接口 2. 继承 DefaultErrorAttributes(推荐),因为 DefaultErrorAttributes 中对异 常数据的处理已经完成,开发者可以直接使用。 具体定义如下: @Component public class MyErrorAttributes extends DefaultErrorAttributes { @Override public Map<String, Object> getErrorAttributes(WebRequest webRequest, Boolean includeStackTrace) { Map<String, Object> map = super.getErrorAttributes(webRequest, includeStackTrace); if ((Integer)map.get("status") == 500) { map.put("message", "服务器内部错误!"); } return map; } } 定义好的 ErrorAttributes 一定要注册成一个 Bean ,这样,Spring Boot 就不会使用默认的 DefaultErrorAttributes 了,运行效果如下图: ![1240 4][] # 自定义异常视图 # -------------------- 异常视图默认就是前面所说的静态或者动态页面,这个也是可以自定义的,首先 ,默认的异常视图加载逻辑在 org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController 类的 errorHtml 方法中,这个方法用来返回异常页面+数据,还有另外一个 error 方法,这个方法用来返回异常数据(如果是 ajax 请求,则该方法会被触发)。 @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, isIncludeStackTrace(request, MediaType.TEXT_HTML))); response.setStatus(status.value()); ModelAndView modelAndView = resolveErrorView(request, response, status, model); return (modelAndView != null) ? modelAndView : new ModelAndView("error", model); } 在该方法中 ,首先会通过 getErrorAttributes 方法去获取异常数据(实际上会调用到 ErrorAttributes 的实例 的 getErrorAttributes 方法),然后调用 resolveErrorView 去创建一个 ModelAndView ,如果这里创建失败,那么用户将会看到默认的错误提示页面。 正常情况下, resolveErrorView 方法会来到 DefaultErrorViewResolver 类的 resolveErrorView 方法中: @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); } return modelAndView; } 在这里,首先以异常响应码作为视图名分别去查找动态页面和静态页面,如果没有查找到,则再以 4xx 或者 5xx 作为视图名再去分别查找动态或者静态页面。 要自定义异常视图解析,也很容易 ,由于 DefaultErrorViewResolver 是在 ErrorMvcAutoConfiguration 类中提供的实例,即开发者没有提供相关实例时,会使用默认的 DefaultErrorViewResolver ,开发者提供了自己的 ErrorViewResolver 实例后,默认的配置就会失效,因此,自定义异常视图,只需要提供 一个 ErrorViewResolver 的实例即可: @Component public class MyErrorViewResolver extends DefaultErrorViewResolver { public MyErrorViewResolver(ApplicationContext applicationContext, ResourceProperties resourceProperties) { super(applicationContext, resourceProperties); } @Override public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) { return new ModelAndView("/aaa/123", model); } } 实际上,开发者也可以在这里定义异常数据(直接在 resolveErrorView 方法重新定义一个 model ,将参数中的model 数据拷贝过去并修改,注意参数中的 model 类型为 UnmodifiableMap,即不可以直接修改),而不需要自定义MyErrorAttributes。定义完成后,提供一个名为123的视图,如下图: ![1240 5][] 如此之后,错误试图就算定义成功了。 # 总结 # -------------------- 实际上也可以自定义异常控制器 BasicErrorController ,不过松哥觉得这样太大动干戈了,没必要,前面几种方式已经可以满足我们的大部分开发需求了。 # 读者福利: # -------------------- **分享免费学习资料** **针对于Java程序员,我这边准备免费的Java架构学习资料(里面有高可用、高并发、高性能及分布式、Jvm性能调优、MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多个知识点的架构资料)** **为什么某些人会一直比你优秀,是因为他本身就很优秀还一直在持续努力变得更优秀,而你是不是还在满足于现状内心在窃喜!希望读到这的您能点个小赞和关注下我,以后还会更新技术干货,谢谢您的支持!** **资料领取方式:加入Java技术交流群`963944895`,[点击加入群聊][Link 1],私信管理员即可免费领取** [1240]: /images/20220217/711ab77745374b3da6aee2253981edf9.png [1240 1]: /images/20220217/8bf30e82dff642bc9daf4226cc79ae04.png [1240 2]: /images/20220217/e94750ad9f524188bc66bc96410e6a8b.png [1240 3]: /images/20220217/238b48619a3a407ea47b0728a6ec44a1.png [1240 4]: /images/20220217/c00c3c4d2d5f4964acf9dbbe7d255eba.png [1240 5]: /images/20220217/8b99f9458c024d10bda7c50d78356506.png [Link 1]: https://jq.qq.com/?_wv=1027&k=5S3Yy38
还没有评论,来说两句吧...