SpringBoot学习笔记——实现全局异常处理
一、前言
在项目开发中会处理各种类型的异常,那么实现全局统一异常处理是十分必要的。Spring Boot提供了一个默认的映射:/error,当抛出异常之后,会转到该请求处理。在实际应用中,并不是很符合项目要求。网上SpringBoot实现全局异常处理的资料有很多,但也不能拿来就用,需要结合自身项目做些改动。这里就记录一下结合网上众多资料实现自己项目的全局异常处理实践过程。
二、SpringBoot异常处理
2.1 官方说明
SpringBoot 2.2.6 官网文档
默认情况下,Spring Boot提供了一个 /error 映射,它以一种合理的方式处理所有错误,并且它在servlet容器中注册为一个“全局”错误页面。对于机器客户端,它生成一个JSON响应,其中包含错误、HTTP状态和异常消息的详细信息。对于浏览器客户端,有一个“whitelabel”错误视图,它以HTML格式呈现相同的数据(要自定义它,需要添加一个解析为错误的视图)。要完全替换默认行为,可以实现ErrorController并注册该类型的bean定义或者添加ErrorAttributes类型的bean以使用现有机制,但要替换内容。
还可以定义一个带有@ControllerAdvice注解的类,以自定义JSON文档以返回特定的控制器和/或异常类型。
默认下,SpringBoot提供了一个 /error 映射,当我们系统发生异常,直接转到 /error 。原始错误页面很简陋,用户体验很不友好。如果想要显示给定状态码的自定义HTML错误页,可以将文件添加到error文件夹。错误页面可以是静态HTML(即添加到任何静态资源文件夹下),也可以使用模板构建。
如果替换默认实现异常处理给了两种方式:
- 实现ErrorContoller
- @ControllerAdvice和@ExceptionHandler注解组合使用
官网给出了@ControllerAdvice和@ExceptionHandler注解实现示例:
@ControllerAdvice(basePackageClasses = AcmeController.class)
public class AcmeControllerAdvice extends ResponseEntityExceptionHandler {
@ExceptionHandler(YourException.class)
@ResponseBody
ResponseEntity<?> handleControllerException(HttpServletRequest request, Throwable ex) {
HttpStatus status = getStatus(request);
return new ResponseEntity<>(new CustomErrorType(status.value(), ex.getMessage()), status);
}
private HttpStatus getStatus(HttpServletRequest request) {
Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");
if (statusCode == null) {
return HttpStatus.INTERNAL_SERVER_ERROR;
}
return HttpStatus.valueOf(statusCode);
}
}
2.2 @ControllerAdvice注解和@ExceptionHandler注解
这里我使用@ControllerAdvice注解实现全局异常处理。使用注解看着代码简洁。
@ControllerAdvice:这是一个增强的 Controller。使用这个 Controller ,可以实现三个方面的功能:
- 全局异常处理
- 全局数据绑定
- 全局数据预处理
灵活使用这三个功能,可以帮助我们简化很多工作,需要注意的是,这是SpringMVC提供的功能,在SpringBoot中可以直接使用。
@ExceptionHandler:统一处理某一类异常,从而能够减少代码重复率和复杂度。
三、实现全局异常处理
3.1 环境说明
开发工具:IDEA 2019.3.1
框架版本:SpringBoot 2.2.6
前端组件库:BootStrap 4.4.1
3.2 具体实现
由于项目中使用jQuery Address实现全站AJAX请求,所以全局异常处理只考虑Ajax请求,普通页面请求不做实现。
GlobalExceptionHandler - 全局异常统一处理类
代码很少,主要实现全局异常处理和自定义异常处理,统一返回结果集,都封装在ReturnMsg类中。
/**
* <p>Title: GlobalExceptionHandler</p>
* <p>Description: 全局异常统一处理</p>
* <p>Company: Ongoing蜗牛</p>
*
* @author liyf
* @date 2020年04月16日
*/
@ControllerAdvice
public class GlobalExceptionHandler {
private Logger logger = LoggerFactory.getLogger(this.getClass());
/**
* 全局异常处理
*
* @param request
* @param e
* @return
*/
@ExceptionHandler(Exception.class)
@ResponseBody
public ReturnMsg handleException(HttpServletRequest request, Exception e) {
// 请求状态
HttpStatus status = getStatus(request);
// 返回错误信息:失败代码、失败信息、具体代码、具体信息
return new ReturnMsg(MsgUtil.MSG_ERROR, "发生异常", String.valueOf(status.value()), status.getReasonPhrase());
}
/**
* 自定义异常处理
*
* @param myException
* @return
*/
@ExceptionHandler(value = MyException.class)
@ResponseBody
public ReturnMsg myErrorHandler(MyException myException) {
// 返回错误信息:失败代码、失败信息、具体代码、具体信息
return new ReturnMsg(MsgUtil.MSG_ERROR, "发生异常", String.valueOf(myException.getStatus()), myException.getReason());
}
/**
* 获取请求状态
*
* @param request
* @return
*/
private HttpStatus getStatus(HttpServletRequest request) {
Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");
if (statusCode == null) {
return HttpStatus.INTERNAL_SERVER_ERROR;
}
return HttpStatus.valueOf(statusCode);
}
}
ReturnMsg - 响应结果类
public class ReturnMsg {
/**
* 结果代码
*/
private String code;
/**
* 结果信息
*/
private String msg;
/**
* 信息代码
*/
private String subCode;
/**
* 信息说明
*/
private String subMsg;
/**
* 返回对象
*/
private Object data;
// 略......
}
MyException - 自定义异常类
继承了Exception类,其定义属性参考springboot默认异常处理时发送的json数据项。
public class MyException extends Exception{
/**
* 异常状态码
*/
private String status;
/**
* 异常原因
*/
private String reason;
/**
* 异常信息
*/
private String error;
/**
* 异常路径
*/
private String path;
// 略......
}
前台页面接收处理异常
全站Ajax请求,所以可以通过设置全局Ajax来对异常进行前端处理。
/**
* 设置全局 AJAX 默认选项
*/
$.ajaxSetup({
contentType:"application/x-www-form-urlencoded;charset=utf-8",
complete:function(XMLHttpRequest, status){
// 全局Ajax访问,处理Ajax清求异常
var res = XMLHttpRequest.responseText;
if (typeof JSON.parse(res) == "object") {
var jsonData = JSON.parse(res);
// 判断字段是否存在
if(jsonData.hasOwnProperty("code") && jsonData.code == "ERROR") {
// 异常信息模态框显示
$('#exceptionMsgModal h1#subCode').text(jsonData.subCode);
$('#exceptionMsgModal h3#subMsg').text(jsonData.subMsg);
$('#exceptionMsgModal').modal('show');
}
}
}
});
3.3 测试效果
全局异常
/**
* 系统用户 列表页
*
* @param model
* @return
*/
@WebLog(channel = "web", name = "系统用户列表", action = "/sysUser", saveFlag = true)
@GetMapping("")
public String list(SysUserCriteria sysUserCriteria, Model model) throws MyException {
// 异常
int i = 1/0;
model.addAttribute("sysUserCriteria", sysUserCriteria);
return "sysUser/list";
}
自定义异常
/**
* 系统用户 列表页
*
* @param model
* @return
*/
@WebLog(channel = "web", name = "系统用户列表", action = "/sysUser", saveFlag = true)
@GetMapping("")
public String list(Model model) throws MyException {
if (1 > 0){
throw new MyException("10001", "审核异常", "审核信息缺失", "/person/check");
}
return "sysUser/list";
}
四、总结说明
一个问题的解决方案有千万种,选择适合自己的。
解决问题的资料不能单纯的拿来就用,改改为己所用。
办法总比困难多,加油,菜鸟。