SpringBoot中的全局异常处理

本篇要点

  • 介绍SpringBoot默认的异常处理机制。
  • 如何定义错误页面。
  • 如何自定义异常数据。
  • 如何自定义视图解析。
  • 介绍@ControllerAdvice注解处理异常。

一、SpringBoot默认的异常处理机制

默认情况下,SpringBoot为以下两种情况提供了不同的响应方式:

  1. Browser Clients浏览器客户端:通常情况下请求头中的Accept会包含text/html,如果未定义/error的请求处理,就会出现如下html页面:Whitelabel Error Page,关于error页面的定制,接下来会详细介绍。

  1. Machine Clients机器客户端:Ajax请求,返回ResponseEntity实体json字符串信息。
  1. {
  2. "timestamp": "2020-10-30T15:01:17.353+00:00",
  3. "status": 500,
  4. "error": "Internal Server Error",
  5. "trace": "java.lang.ArithmeticException: / by zero...",
  6. "message": "/ by zero",
  7. "path": "/"
  8. }

SpringBoot默认提供了程序出错的结果映射路径/error,这个请求的处理逻辑在BasicErrorController中处理,处理逻辑如下:

  1. // 判断mediaType类型是否为text/html
  2. @RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
  3. public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
  4. HttpStatus status = getStatus(request);
  5. Map<String, Object> model = Collections
  6. .unmodifiableMap(getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.TEXT_HTML)));
  7. response.setStatus(status.value());
  8. // 创建ModelAndView对象,返回页面
  9. ModelAndView modelAndView = resolveErrorView(request, response, status, model);
  10. return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
  11. }
  12. @RequestMapping
  13. public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
  14. HttpStatus status = getStatus(request);
  15. if (status == HttpStatus.NO_CONTENT) {
  16. return new ResponseEntity<>(status);
  17. }
  18. Map<String, Object> body = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL));
  19. return new ResponseEntity<>(body, status);
  20. }

二、错误页面的定制

相信Whitelabel Error Pag页面我们经常会遇到,这样体验不是很好,在SpringBoot中可以尝试定制错误页面,定制方式主要分静态和动态两种:

  1. 静态异常页面

classpath:/public/errorclasspath:/static/error路径下定义相关页面:文件名应为确切的状态代码,如404.html,或系列掩码,如4xx.html。

举个例子,如果你想匹配404或5开头的所有状态代码到静态的html文件,你的文件目录应该是下面这个样子:

  1. src/
  2. +- main/
  3. +- java/
  4. | + <source code>
  5. +- resources/
  6. +- public/
  7. +- error/
  8. | +- 404.html
  9. | +- 5xx.html
  10. +- <other public assets>

如果500.html和5xx.html同时生效,那么优先展示500.html页面。

  1. 使用模板的动态页面

放置在classpath:/templates/error路径下:这里使用thymeleaf模板举例:

  1. src/
  2. +- main/
  3. +- java/
  4. | + <source code>
  5. +- resources/
  6. +- templates/
  7. +- error/
  8. | +- 5xx.html
  9. +- <other templates>

页面如下:

  1. <!DOCTYPE html>
  2. <html lang="en" xmlns:th="http://www.thymeleaf.org">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>5xx</title>
  6. </head>
  7. <body>
  8. <h1>5xx</h1>
  9. <p th:text="'error -->'+ ${error}"></p>
  10. <p th:text="'status -->' + ${status}"></p>
  11. <p th:text="'timestamp -->' + ${timestamp}"></p>
  12. <p th:text="'message -->' + ${message}"></p>
  13. <p th:text="'path -->' +${path}"></p>
  14. <p th:text="'trace -->' + ${trace}"></p>
  15. </body>
  16. </html>

如果静态页面和动态页面同时存在且都能匹配,SpringBoot对于错误页面的优先展示规则如下:

如果发生了500错误:

动态500 -> 静态500 -> 动态5xx -> 静态5xx

三、自定义异常数据

默认的数据主要是以下几个,这些数据定义在org.springframework.boot.web.servlet.error.DefaultErrorAttributes中,数据的定义在getErrorAttributes方法中。

DefaultErrorAttributes类在org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration自动配置类中定义:

  1. @Bean
  2. @ConditionalOnMissingBean(value = ErrorAttributes.class,
  3. search = SearchStrategy.CURRENT)
  4. public DefaultErrorAttributes errorAttributes() {
  5. return new DefaultErrorAttributes();
  6. }

如果我们没有提供ErrorAttributes的实例,SpringBoot默认提供一个DefaultErrorAttributes实例。

因此,我们就该知道如何去自定义异常数据属性:

  1. 实现ErrorAttributes接口。
  2. 继承DefaultErrorAttributes,本身已经定义对异常数据的处理,继承更具效率。

定义方式如下:

  1. /**
  2. * 自定义异常数据
  3. * @author Summerday
  4. */
  5. @Slf4j
  6. @Component
  7. public class MyErrorAttributes extends DefaultErrorAttributes {
  8. @Override
  9. public Map<String, Object> getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) {
  10. Map<String, Object> map = super.getErrorAttributes(webRequest, options);
  11. if(map.get("status").equals(500)){
  12. log.warn("服务器内部异常");
  13. }
  14. return map;
  15. }
  16. }

四、自定义异常视图

自定义视图的加载逻辑存在于BasicErrorController类的errorHtml方法中,用于返回一个ModelAndView对象,这个方法中,首先通过getErrorAttributes获取到异常数据,然后调用resolveErrorView去创建一个ModelAndView对象,只有创建失败的时候,用户才会看到默认的错误提示页面。

  1. @RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
  2. public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
  3. HttpStatus status = getStatus(request);
  4. Map<String, Object> model = Collections
  5. .unmodifiableMap(
  6. //ErrorAttributes # getErrorAttributes
  7. getErrorAttributes(request,
  8. getErrorAttributeOptions(request, MediaType.TEXT_HTML)));
  9. response.setStatus(status.value());
  10. // E
  11. ModelAndView modelAndView = resolveErrorView(request, response, status, model);
  12. return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
  13. }

DefaultErrorViewResolver类的resolveErrorView方法:

  1. @Override
  2. public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
  3. // 以异常状态码作为视图名去locations路径中去查找页面
  4. ModelAndView modelAndView = resolve(String.valueOf(status.value()), model);
  5. // 如果找不到,以4xx,5xx series去查找
  6. if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
  7. modelAndView = resolve(SERIES_VIEWS.get(status.series()), model);
  8. }
  9. return modelAndView;
  10. }

那么如何自定义呢?和自定义异常数据相同,如果我们定义了一个ErrorViewResolver的实例,默认的配置就会失效。

  1. /**
  2. * 自定义异常视图解析
  3. * @author Summerday
  4. */
  5. @Component
  6. public class MyErrorViewResolver extends DefaultErrorViewResolver {
  7. public MyErrorViewResolver(ApplicationContext applicationContext, ResourceProperties resourceProperties) {
  8. super(applicationContext, resourceProperties);
  9. }
  10. @Override
  11. public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
  12. return new ModelAndView("/hyh/resolve",model);
  13. }
  14. }

此时,SpringBoot将会去/hyh目录下寻找resolve.html页面。

五、@ControllerAdvice注解处理异常

前后端分离的年代,后端往往需要向前端返回统一格式的json信息,以下为封装的AjaxResult对象:

  1. public class AjaxResult extends HashMap<String, Object> {
  2. //状态码
  3. public static final String CODE_TAG = "code";
  4. //返回内容
  5. public static final String MSG_TAG = "msg";
  6. //数据对象
  7. public static final String DATA_TAG = "data";
  8. private static final long serialVersionUID = 1L;
  9. public AjaxResult() {
  10. }
  11. public AjaxResult(int code, String msg) {
  12. super.put(CODE_TAG, code);
  13. super.put(MSG_TAG, msg);
  14. }
  15. public AjaxResult(int code, String msg, Object data) {
  16. super.put(CODE_TAG, code);
  17. super.put(MSG_TAG, msg);
  18. if (data != null) {
  19. super.put(DATA_TAG, data);
  20. }
  21. }
  22. public static AjaxResult ok() {
  23. return AjaxResult.ok("操作成功");
  24. }
  25. public static AjaxResult ok(Object data) {
  26. return AjaxResult.ok("操作成功", data);
  27. }
  28. public static AjaxResult ok(String msg) {
  29. return AjaxResult.ok(msg, null);
  30. }
  31. public static AjaxResult ok(String msg, Object data) {
  32. return new AjaxResult(HttpStatus.OK.value(), msg, data);
  33. }
  34. public static AjaxResult error() {
  35. return AjaxResult.error("操作失败");
  36. }
  37. public static AjaxResult error(String msg) {
  38. return AjaxResult.error(msg, null);
  39. }
  40. public static AjaxResult error(String msg, Object data) {
  41. return new AjaxResult(HttpStatus.INTERNAL_SERVER_ERROR.value(), msg, data);
  42. }
  43. public static AjaxResult error(int code, String msg) {
  44. return new AjaxResult(code, msg, null);
  45. }
  46. }

根据业务的需求不同,我们往往也需要自定义异常类,便于维护:

  1. /**
  2. * 自定义异常
  3. *
  4. * @author Summerday
  5. */
  6. public class CustomException extends RuntimeException {
  7. private static final long serialVersionUID = 1L;
  8. private Integer code;
  9. private final String message;
  10. public CustomException(String message) {
  11. this.message = message;
  12. }
  13. public CustomException(String message, Integer code) {
  14. this.message = message;
  15. this.code = code;
  16. }
  17. public CustomException(String message, Throwable e) {
  18. super(message, e);
  19. this.message = message;
  20. }
  21. @Override
  22. public String getMessage() {
  23. return message;
  24. }
  25. public Integer getCode() {
  26. return code;
  27. }
  28. }

@ControllerAdvice和@RestControllerAdvice这俩注解的功能之一,就是做到Controller层面的异常处理,而两者的区别,与@Controller和@RestController差不多。

@ExceptionHandler指定需要处理的异常类,针对自定义异常,如果是ajax请求,返回json信息,如果是普通web请求,返回ModelAndView对象。

  1. /**
  2. * 全局异常处理器
  3. * @author Summerday
  4. */
  5. @RestControllerAdvice
  6. public class GlobalExceptionHandler {
  7. private static final Logger log =
  8. LoggerFactory.getLogger(GlobalExceptionHandler.class);
  9. @ExceptionHandler(CustomException.class)
  10. public Object handle(HttpServletRequest request, CustomException e) {
  11. AjaxResult info = AjaxResult.error(e.getMessage());
  12. log.error(e.getMessage());
  13. // 判断是否为ajax请求
  14. if (isAjaxRequest(request)) {
  15. return info;
  16. }
  17. ModelAndView mv = new ModelAndView();
  18. mv.setViewName("custom"); // templates/custom.html
  19. mv.addAllObjects(info);
  20. mv.addObject("url", request.getRequestURL());
  21. return mv;
  22. }
  23. private boolean isAjaxRequest(HttpServletRequest request) {
  24. return "XMLHttpRequest".equals(request.getHeader("X-Requested-With"));
  25. }
  26. }

在Controller层,人为定义抛出异常:

  1. @RestController
  2. public class TestController {
  3. @GetMapping("/ajax")
  4. public AjaxResult ajax() {
  5. double alpha = 0.9;
  6. if (Math.random() < alpha) {
  7. throw new CustomException("自定义异常!");
  8. }
  9. return AjaxResult.ok();
  10. }
  11. }

最后,通过/templates/custom.html定义的动态模板页面展示数据:

  1. <!DOCTYPE html>
  2. <html lang="en" xmlns:th="http://www.thymeleaf.org">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>自定义界面</title>
  6. </head>
  7. <body>
  8. <p th:text="'msg -->'+ ${msg}"></p>
  9. <p th:text="'code -->'+ ${code}"></p>
  10. <p th:text="'url -->'+ ${url}"></p>
  11. </body>
  12. </html>

源码下载

本文内容均为对优秀博客及官方文档总结而得,原文地址均已在文中参考阅读处标注。最后,文中的代码样例已经全部上传至Gitee:https://gitee.com/tqbx/springboot-samples-learn。

参考阅读

SpringBoot中的全局异常处理的更多相关文章

  1. SpringBoot优雅的全局异常处理

    前言 本篇文章主要介绍的是SpringBoot项目进行全局异常的处理. SpringBoot全局异常准备 说明:如果想直接获取工程那么可以直接跳到底部,通过链接下载工程代码. 开发准备 环境要求 JD ...

  2. 在.NET Core程序中设置全局异常处理

    以前我们想设置全局异常处理只需要这样的代码: AppDomain currentDomain = AppDomain.CurrentDomain; currentDomain.UnhandledExc ...

  3. 在SpringBoot中配置全局捕获异常

    前言 之前写过一篇博客是使用spring利用HandlerExceptionResolver实现全局异常捕获 里面使用spring的HandlerExceptionResolver接口来实现全局的异常 ...

  4. Global Error Handling in ASP.NET Web API 2(webapi2 中的全局异常处理)

    目前,在Web API中没有简单的方法来记录或处理全局异常(webapi1中).一些未处理的异常可以通过exception filters进行处理,但是有许多情况exception filters无法 ...

  5. WEB 项目中的全局异常处理

    在web 项目中,遇到异常一般有两种处理方式:try.....catch....:throw 通常情况下我们用try.....catch.... 对异常进行捕捉处理,可是在实际项目中随时的进行异常捕捉 ...

  6. 【5min+】AspNet Core中的全局异常处理

    系列介绍 [五分钟的dotnet]是一个利用您的碎片化时间来学习和丰富.net知识的博文系列.它所包含了.net体系中可能会涉及到的方方面面,比如C#的小细节,AspnetCore,微服务中的.net ...

  7. ASP.NET Core MVC 中设置全局异常处理方式

    在asp.net core mvc中,如果有未处理的异常发生后,会返回http500错误,对于最终用户来说,显然不是特别友好.那如何对于这些未处理的异常显示统一的错误提示页面呢? 在asp.net c ...

  8. 7.2WebApi2中的全局异常处理

    现在在WebApi中还没有一种简单的方式去记录或者处理全局的异常.一些未处理的异常可以处理通过异常过滤器,但有大量的异常是异常过滤器不能处理的.例如: 从控制器的构造函数引发的异常. 从消息处理程序引 ...

  9. Spring Boot2 系列教程(十三)Spring Boot 中的全局异常处理

    在 Spring Boot 项目中 ,异常统一处理,可以使用 Spring 中 @ControllerAdvice 来统一处理,也可以自己来定义异常处理方案.Spring Boot 中,对异常的处理有 ...

随机推荐

  1. ElasticSearch 简单的crud查询

    //数据库和es的对应关系(学习文档可以参考https://es.xiaoleilu.com/010_Intro/35_Tutorial_Aggregations.html) //如下接口调用都是使用 ...

  2. nginx 1.12 HTTPS双向认证配置

    使用openssl生成相关证书: #生成CA私钥,私钥会被加密,需要设置密码 openssl genrsa -aes256 -out ca.key 2048 #生成CA证书签名请求,需要输入CA私钥密 ...

  3. MATLAB鼠标事件

    来源:https://blog.csdn.net/weixin_39090239/article/details/80586930 前记: 人机交互的方式--键盘开关.鼠标.触摸屏.体感传感器(Kin ...

  4. 联赛模拟测试12 C. sum 莫队+组合数

    题目描述 分析 \(80\) 分的暴力都打出来了还是没有想到莫队 首先对于 \(s[n][m]\) 我们可以很快地由它推到 \(s[n][m+1]\) 和 \(s[n][m-1]\) 即 \(s[n] ...

  5. Black-Lives-Matter-Resources

    下载 Black-Lives-Matter-ResourcesBlack-Lives-Matter-Resources 关于最近在美国发生的事件的资源列表 链接 描述 由于(可选) 插入链接 在这里插 ...

  6. Linux 杀毒软件ClamAV安装部署

    环境说明 系统安全需求,批量安装免费杀毒软件: 操作系统统一为CentOS 7 x64,在此选择免费开源杀毒软件ClamAV: 两种安装方式 1.yum 安装: 2.源码包编译安装: 安装参考网址: ...

  7. 手把手教你AspNetCore WebApi:认证与授权

    前言 这几天小明又有烦恼了,之前给小红的接口没有做认证授权,直接裸奔在线上,被马老板发现后狠狠的骂了一顿,赶紧让小明把授权加上.赶紧Baidu一下,发现大家都在用JWT认证授权,这个倒是挺适合自己的. ...

  8. .Net Core 读取,导入 excel数据 officeopenxml

    /// <summary> /// 导出Excel /// </summary> /// <param name="path">路径</p ...

  9. CSS的元素显示模式与转换

    CSS的元素显示模式与转换 1. CSS的元素显示模式 1.1 块元素 <div>标签是最典型的块元素.另外常见的块元素有h1~h6.p.ul.ol.li等. 特点: 独占一行 高度.宽度 ...

  10. day30 Pyhton 面向对象 反射

    @property # 例1 - 1 (某一个属性如果是通过计算得来的,那么计算的过程写在方法里,把这个方法伪装成属性) from math import pi # class Circle: # d ...