SpringBoot中的全局异常处理

本篇要点

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

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

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

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

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

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

// 判断mediaType类型是否为text/html
@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, getErrorAttributeOptions(request, MediaType.TEXT_HTML)));
response.setStatus(status.value());
// 创建ModelAndView对象,返回页面
ModelAndView modelAndView = resolveErrorView(request, response, status, model);
return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
} @RequestMapping
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
HttpStatus status = getStatus(request);
if (status == HttpStatus.NO_CONTENT) {
return new ResponseEntity<>(status);
}
Map<String, Object> body = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL));
return new ResponseEntity<>(body, status);
}

二、错误页面的定制

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

  1. 静态异常页面

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

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

src/
+- main/
+- java/
| + <source code>
+- resources/
+- public/
+- error/
| +- 404.html
| +- 5xx.html
+- <other public assets>

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

  1. 使用模板的动态页面

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

src/
+- main/
+- java/
| + <source code>
+- resources/
+- templates/
+- error/
| +- 5xx.html
+- <other templates>

页面如下:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>5xx</title>
</head>
<body>
<h1>5xx</h1>
<p th:text="'error -->'+ ${error}"></p>
<p th:text="'status -->' + ${status}"></p>
<p th:text="'timestamp -->' + ${timestamp}"></p>
<p th:text="'message -->' + ${message}"></p>
<p th:text="'path -->' +${path}"></p>
<p th:text="'trace -->' + ${trace}"></p>
</body>
</html>

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

如果发生了500错误:

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

三、自定义异常数据

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

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

	@Bean
@ConditionalOnMissingBean(value = ErrorAttributes.class,
search = SearchStrategy.CURRENT)
public DefaultErrorAttributes errorAttributes() {
return new DefaultErrorAttributes();
}

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

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

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

定义方式如下:

/**
* 自定义异常数据
* @author Summerday
*/
@Slf4j
@Component
public class MyErrorAttributes extends DefaultErrorAttributes { @Override
public Map<String, Object> getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) {
Map<String, Object> map = super.getErrorAttributes(webRequest, options);
if(map.get("status").equals(500)){
log.warn("服务器内部异常");
}
return map;
}
}

四、自定义异常视图

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

@RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
HttpStatus status = getStatus(request);
Map<String, Object> model = Collections
.unmodifiableMap(
//ErrorAttributes # getErrorAttributes
getErrorAttributes(request,
getErrorAttributeOptions(request, MediaType.TEXT_HTML)));
response.setStatus(status.value());
// E
ModelAndView modelAndView = resolveErrorView(request, response, status, model);
return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
}

DefaultErrorViewResolver类的resolveErrorView方法:

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

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

/**
* 自定义异常视图解析
* @author Summerday
*/ @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("/hyh/resolve",model);
}
}

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

五、@ControllerAdvice注解处理异常

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

public class AjaxResult extends HashMap<String, Object> {

    //状态码
public static final String CODE_TAG = "code"; //返回内容
public static final String MSG_TAG = "msg"; //数据对象
public static final String DATA_TAG = "data"; private static final long serialVersionUID = 1L; public AjaxResult() {
} public AjaxResult(int code, String msg) {
super.put(CODE_TAG, code);
super.put(MSG_TAG, msg);
} public AjaxResult(int code, String msg, Object data) {
super.put(CODE_TAG, code);
super.put(MSG_TAG, msg);
if (data != null) {
super.put(DATA_TAG, data);
}
} public static AjaxResult ok() {
return AjaxResult.ok("操作成功");
} public static AjaxResult ok(Object data) {
return AjaxResult.ok("操作成功", data);
} public static AjaxResult ok(String msg) {
return AjaxResult.ok(msg, null);
} public static AjaxResult ok(String msg, Object data) {
return new AjaxResult(HttpStatus.OK.value(), msg, data);
} public static AjaxResult error() {
return AjaxResult.error("操作失败");
} public static AjaxResult error(String msg) {
return AjaxResult.error(msg, null);
} public static AjaxResult error(String msg, Object data) {
return new AjaxResult(HttpStatus.INTERNAL_SERVER_ERROR.value(), msg, data);
} public static AjaxResult error(int code, String msg) {
return new AjaxResult(code, msg, null);
}
}

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

/**
* 自定义异常
*
* @author Summerday
*/
public class CustomException extends RuntimeException { private static final long serialVersionUID = 1L; private Integer code; private final String message; public CustomException(String message) {
this.message = message;
} public CustomException(String message, Integer code) {
this.message = message;
this.code = code;
} public CustomException(String message, Throwable e) {
super(message, e);
this.message = message;
} @Override
public String getMessage() {
return message;
} public Integer getCode() {
return code;
}
}

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

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

/**
* 全局异常处理器
* @author Summerday
*/ @RestControllerAdvice
public class GlobalExceptionHandler { private static final Logger log =
LoggerFactory.getLogger(GlobalExceptionHandler.class); @ExceptionHandler(CustomException.class)
public Object handle(HttpServletRequest request, CustomException e) {
AjaxResult info = AjaxResult.error(e.getMessage());
log.error(e.getMessage());
// 判断是否为ajax请求
if (isAjaxRequest(request)) {
return info;
}
ModelAndView mv = new ModelAndView();
mv.setViewName("custom"); // templates/custom.html
mv.addAllObjects(info);
mv.addObject("url", request.getRequestURL());
return mv;
} private boolean isAjaxRequest(HttpServletRequest request) {
return "XMLHttpRequest".equals(request.getHeader("X-Requested-With"));
}
}

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

@RestController
public class TestController { @GetMapping("/ajax")
public AjaxResult ajax() {
double alpha = 0.9;
if (Math.random() < alpha) {
throw new CustomException("自定义异常!");
}
return AjaxResult.ok();
}
}

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

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>自定义界面</title>
</head>
<body>
<p th:text="'msg -->'+ ${msg}"></p>
<p th:text="'code -->'+ ${code}"></p>
<p th:text="'url -->'+ ${url}"></p>
</body>
</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. UCanCode发布升级E-Form++可视化源码组件库2020全新版 !

    2020年. 中国.成都 UCanCode发布升级E-Form++可视化源码组件库2020全新版 ! --- 全面性能提升,UCanCode有史以来最强大的版本发布! E-Form++可视化源码组件库 ...

  2. Oracle12c 使用总结

    /*创建临时表空间 */create temporary tablespace BBB tempfile 'D:\APP\oracle\oradata\orcl\BBB.dbf' size 500m ...

  3. Spring Boot第四弹,一文教你如何无感知切换日志框架?

    持续原创输出,点击上方蓝字关注我吧 目录 前言 Spring Boot 版本 什么是日志门面? 如何做到无感知切换? 如何切换? 引入依赖 指定配置文件 日志如何配置? 总结 前言 首先要感谢一下读者 ...

  4. list_for_eacy_entry图解

    .

  5. Linux学习笔记-vi(一)

    vim编辑命令 vim命令的三种模式: 1.命令模式: vi file.txt  进入vi模式,默认为命令模式,命令模式移动光标. 2.插入模式 i (insert):在光标前插入内容 a(appen ...

  6. 使用SSM框架实现图片的上传

    SSM实现图片上传功能 效果 在前端页面点击上传图片功能按钮,即弹出文件管理器,选择图片并上传: 思路 在前端页面添加 input 标签,type选择file. 在后端controller编写方法. ...

  7. python单元测试框架pytest

    首先祝大家国庆节日快乐,这个假期因为我老婆要考注会,我也跟着天天去图书馆学了几天,学习的感觉还是非常不错的,这是一篇总结. 这篇博客准备讲解一下pytest测试框架,这个框架是当前最流行的python ...

  8. 001 01 Android 零基础入门 01 Java基础语法 01 Java初识 01 导学

    001 01 Android 零基础入门 01 Java基础语法 01 Java初识 01 导学 welcome to Java World 欢迎来到Java世界 一起领略Java编程世界的奥秘与奥妙 ...

  9. C++vector and opencv Mat

    转载:https://blog.csdn.net/u012507022/article/details/50979011?utm_source=blogxgwz5 最近在写Opencv程序,用到离散小 ...

  10. C/C++ 条件编译

    条件编译就是指有条件的编译,即根据条件去编译代码,在编译阶段时就对代码做出取舍,有的编译,有的不编译,这样比写成一个个判断函数更有效率,比如工程代码大部分的地方都类似,只有个别语句因为使用的硬件版本不 ...