统一异常处理
- 1. 说明
- 2. 问题描述
- 3. 异常处理器使用
- 3.1 创建异常处理器类
- 3.2 让程序抛出异常
- 3.3 测试
- 4. 项目异常处理方案
- 4.1 异常分类
- 4.2 异常解决方案
- 4.3 异常解决方案的具体实现
- 4.4 测试
- 5. 总结
1. 说明
\quad 本篇文章是在文章SpringMVC:SSM整合(SpringMVC+Spring+Mybatis)案例(9)和SpringMVC:统一封装结果(10)的基础上进行讲解的,建议在读本篇文章之前建议预先了解这两篇文章。
2. 问题描述
前端接收到这个信息后和之前我们约定的格式(统一返回Result类)不一致,这个问题该如何解决?
在解决问题之前,我们先来看下异常的种类及出现异常的原因:
- 框架内部抛出的异常:因使用不合规导致
- 数据层抛出的异常:因外部服务器故障导致(例如:服务器访问超时)
- 业务层抛出的异常:因业务逻辑书写错误导致(例如:遍历业务书写操作,导致索引异常等)
- 表现层抛出的异常:因数据收集、校验等规则导致(例如:不匹配的数据类型间导致异常)
- 工具类抛出的异常:因工具类书写不严谨不够健壮导致(例如:必要释放的连接长期未释放等)
在开发的任何一个位置都有可能出现异常,而且这些异常是不能避免的。所以我们就得将异常进行处理。
-
各个层级均出现异常,异常处理代码书写在哪一层?
所有的异常均抛出到表现层进行处理
-
异常的种类很多,表现层如何将所有的异常都处理到呢?
异常分类
-
表现层处理异常,每个方法中单独书写,代码书写量巨大且意义不强,如何解决?
AOP
3. 异常处理器使用
3.1 创建异常处理器类
在controller层下创建异常处理器类:
// @RestControllerAdvice用于标识当前类为REST风格对应的异常处理器
// @ControllerAdvice用于标识当前类为普通风格对应的异常处理器
@RestControllerAdvice
public class ProjectExceptionAdvice {
// 除了自定义的异常处理器,保留对Exception类型的异常处理,用于处理非预期的异常
@ExceptionHandler(Exception.class)
public void doException(Exception ex){
System.out.println("出现异常。")
}
}
3.2 让程序抛出异常
修改BookController
的getById方法,添加int i = 1/0
.
@GetMapping("/{id}")
public Result getById(@PathVariable Integer id) {
int i = 1/0;
Book book = bookService.getById(id);
Integer code = book != null ? Code.GET_OK : Code.GET_ERR;
String msg = book != null ? "" : "数据查询失败,请重试!";
return new Result(code,book,msg);
}
3.3 测试
说明异常已经被拦截并执行了doException
方法。
4. 项目异常处理方案
4.1 异常分类
因为异常的种类有很多,如果每一个异常都对应一个@ExceptionHandler,那得写多少个方法来处理各自的异常,所以我们在处理异常之前,需要对异常进行一个分类:
-
业务异常(BusinessException)
-
规范的用户行为产生的异常
- 用户在页面输入内容的时候未按照指定格式进行数据填写,如在年龄框输入的是字符串
-
不规范的用户行为操作产生的异常
- 如用户故意传递错误数据
-
-
系统异常(SystemException)
- 项目运行过程中可预计但无法避免的异常
- 比如数据库或服务器宕机
- 项目运行过程中可预计但无法避免的异常
-
其他异常(Exception)
- 编程人员未预期到的异常,如:用到的文件不存在
将异常分类以后,针对不同类型的异常,要提供具体的解决方案:
4.2 异常解决方案
- 业务异常(BusinessException)
- 发送对应消息传递给用户,提醒规范操作
- 大家常见的就是提示用户名已存在或密码格式不正确等
- 发送对应消息传递给用户,提醒规范操作
- 系统异常(SystemException)
- 发送固定消息传递给用户,安抚用户
- 系统繁忙,请稍后再试
- 系统正在维护升级,请稍后再试
- 系统出问题,请联系系统管理员等
- 发送特定消息给运维人员,提醒维护
- 可以发送短信、邮箱或者是公司内部通信软件
- 记录日志
- 发消息和记录日志对用户来说是不可见的,属于后台程序
- 发送固定消息传递给用户,安抚用户
- 其他异常(Exception)
- 发送固定消息传递给用户,安抚用户
- 发送特定消息给编程人员,提醒维护(纳入预期范围内)
- 一般是程序没有考虑全,比如未做非空校验等
- 记录日志
4.3 异常解决方案的具体实现
思路:
-
先通过自定义异常,完成BusinessException和SystemException的定义
-
将其他异常包装成自定义异常类型
-
在异常处理器类中对不同的异常进行处理
步骤一:定义两个异常处理器
自定义系统异常处理器,用于封装异常信息,对异常进行分类:
public class SystemException extends RuntimeException{
private Integer code;
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public SystemException(Integer code, String message) {
super(message);
this.code = code;
}
public SystemException(Integer code, String message, Throwable cause) {
super(message, cause);
this.code = code;
}
}
自定义业务异常处理器,用于封装异常信息,对异常进行分类:
public class BusinessException extends RuntimeException{
private Integer code;
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public BusinessException(Integer code, String message) {
super(message);
this.code = code;
}
public BusinessException(Integer code, String message, Throwable cause) {
super(message, cause);
this.code = code;
}
}
说明:
- 让自定义异常类继承
RuntimeException
的好处是,后期在抛出这两个异常的时候,就不用在try…catch…或throws了 - 自定义异常类中添加
code
属性的原因是为了更好的区分异常是来自哪个业务的
步骤2:将其他异常包成自定义异常
public Book getById(Integer id) {
//模拟业务异常,包装成自定义异常
if(id == 15){
throw new BusinessException(Code.BUSINESS_ERR,"业务层出现异常");
}
//模拟系统异常,将可能出现的异常进行包装,转换成自定义异常
try{
int i = 1/0;
}catch (Exception e){
throw new SystemException(Code.SYSTEM_TIMEOUT_ERR,"服务器访问超时,请重试!",e);
}
return bookDao.getById(id);
}
状态码Code类:
// 状态码
public class Code {
public static final Integer SAVE_OK = 20011;
public static final Integer SAVE_ERR = 20010;
public static final Integer DELETE_OK = 20021;
public static final Integer DELETE_ERR = 20020;
public static final Integer UPDATE_OK = 20031;
public static final Integer UPDATE_ERR = 20030;
public static final Integer GET_OK = 20041;
public static final Integer GET_ERR = 20040;
public static final Integer SYSTEM_ERR = 50001;
public static final Integer SYSTEM_TIMEOUT_ERR = 60001;
public static final Integer SYSTEM_UNKNOW_ERR = 70001;
public static final Integer BUSINESS_ERR = 80001;
}
步骤3:处理器类中处理自定义异常
package com.itheima.controller;
import com.itheima.exception.BusinessException;
import com.itheima.exception.SystemException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
/**
* @Author Mr.Lu
* @Date 2023/2/11 9:36
* @ClassName ProjectExceptionAdvice
* @Version 1.0
*/
// @RestControllerAdvice用于标识当前类为REST风格对应的异常处理器
@RestControllerAdvice
public class ProjectExceptionAdvice {
@ExceptionHandler(BusinessException.class)
public Result doBusinessException(BusinessException ex){
return new Result(ex.getCode(), null, ex.getMessage());
}
@ExceptionHandler(SystemException.class)
public Result doSystemException(SystemException ex){
// 记录日志
// 发送消息给运维
// 发送邮件给开发人员,ex对象发送给开发人员
return new Result(ex.getCode(), null, ex.getMessage());
}
// 除了自定义的异常处理器,保留对Exception类型的异常处理,用于处理非预期的异常
@ExceptionHandler(Exception.class)
public Result doOtherException(Exception ex){
//记录日志
//发送消息给运维
//发送邮件给开发人员,ex对象发送给开发人员
return new Result(8888,null, "系统繁忙,请稍后再试!");
}
}
4.4 测试
根据ID查询,如果传入的参数为15,会报BusinessException
如果传入的是其他参数,会报SystemException
5. 总结
项目中的异常处理方式: