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/errorclasspath:/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. Kubernetes客户端和管理界面大集合

    今天给大家介绍目前市面上常用的kubernetes管理工具,总有一款适合您~~~ 简介 Kubectl K9s Kubernetes-Dashboard Rancher Kuboard Lens Oc ...

  2. CCNP七层参考模型

    一.OSI七层参考模型 七层参考模型由ISO组织提出,为什么是参考模型呢?因为我们现在实际应用的是TCP/IP协议栈,OSI模型仅供学习参考,下面具体说一下有哪七层: (7)应用层:应用程序和服务功能 ...

  3. linux系统内存爆满的快速解决办法!

    首先用free工具检查一下内存的使用情况: $ free -m total used free shared buff/cache available Mem: 15884 207 573 145 1 ...

  4. 使用IBMMQ时的那些事

    之前项目一直使用的是ActiveMQ,突然要变更成IBMMQ,整理了一些搭建MQ和代码连接MQ是遇到的问题,记录一下. (1)三个系统之内部使用IBMMQ交互,所以应设立两个本地接收队列和两个发送队列 ...

  5. 基于gis的系统开发,程序运行出现问题 ArcGIS product not specified.You must first bind to an ArcGIS version prior to using any ArcGIS components.

    在之前初始化的时候添加下面的一行代码:ESRI.ArcGIS.RuntimeManager.Bind(ESRI.ArcGIS.ProductCode.EngineOrDesktop);还需要添加一个引 ...

  6. Hibernate4.3基础知识1

    一.Hibernate 开发环境搭建 4.3 1.导包    2.创建hibernate.cfg.xml配置文件   3.创建实体类   4.创建映射文件 实体类名.hbm.xml  配置文件 二.h ...

  7. STM32之旅5——IWDG

    STM32之旅5--IWDG stm32有两个看门狗,一个独立看门狗(IWDG).一个窗口看门狗(WWDG):独立看门狗是时钟源是内部的40kHz的低速时钟,即使主频出问题了,独立看门狗也不会受到影响 ...

  8. C#与sql进行图片存取

    1 using System; 2 using System.Collections.Generic; 3 using System.ComponentModel; 4 using System.Da ...

  9. Python+Appium自动化测试(6)-元素等待方法与重新封装元素定位方法

    在appium自动化测试脚本运行的过程中,因为网络不稳定.测试机或模拟器卡顿等原因,有时候会出现页面元素加载超时元素定位失败的情况,但实际这又不是bug,只是元素加载较慢,这个时候我们就会使用元素等待 ...

  10. 如何将python下载源地址修改为国内镜像源

    (1)在  C:\Users\xxx 下面创建新的目录  pip 文件夹 (2)在 pip目录下创建后缀为ini,名为pip的文件,另存为  (pip.ini) 文件内容设置为:(清华源) [glob ...