先来演示个效果,修改UserController类的getById方法
@GetMapping("/{userId}")
public Result findByUserId(@PathVariable Integer userId){
//手动添加一个错误信息
if(userId==10){
int i = 10/0;
}
User user = userService.findByUserId(userId);
if (user != null){
return new Result(user,ResultCode.SELECT_SUCCES,"查询成功");
}
return new Result(null,ResultCode.SELECT_FAIL,"查询失败");
}
重新启动运行项目,使用PostMan发送请求,当传入的id为10,则会出现如下效果:

前端接收到这个信息后和之前我们约定的格式不一致,这个问题该如何解决?
在解决问题之前,我们先来看下异常的种类及出现异常的原因:
看完上面这些出现异常的位置,你会发现,在我们开发的任何一个位置都有可能出现异常,而且这些异常是不能避免的。所以我们就得将异常进行处理。
思考
各个层级均出现异常,异常处理代码书写在哪一层?
所有的异常均抛出到表现层进行处理
异常的种类很多,表现层如何将所有的异常都处理到呢?
异常分类
表现层处理异常,每个方法中单独书写,代码书写量巨大且意义不强,如何解决?
AOP
对于上面这些问题及解决方案,SpringMVC已经为我们提供了一套解决方案:
异常处理器:
集中的、统一的处理项目中出现的异常。

最终创建好的项目结构如下:

//@RestControllerAdvice用于标识当前类为REST风格对应的异常处理器
@RestControllerAdvice
public class ExceptionAdvice {
//除了自定义的异常处理器,保留对Exception类型的异常处理,用于处理非预期的异常
@ExceptionHandler(Exception.class)
public void doException(Exception ex){
System.out.println("异常!!! 发现一个异常!!!");
}
}
确保SpringMvcConfig能够扫描到异常处理器类
修改UserController的findByUserId方法,添加int i = 10/0;。
@GetMapping("/{userId}")
public Result findByUserId(@PathVariable Integer userId){
//手动添加一个错误信息
if(userId==10){
int i = 10/0;
}
User user = userService.findByUserId(userId);
if (user != null){
return new Result(user,ResultCode.SELECT_SUCCES,"查询成功");
}
return new Result(null,ResultCode.SELECT_FAIL,"查询失败");
}

说明异常已经被拦截并执行了doException方法。
//@RestControllerAdvice用于标识当前类为REST风格对应的异常处理器
@RestControllerAdvice
public class ExceptionAdvice {
//除了自定义的异常处理器,保留对Exception类型的异常处理,用于处理非预期的异常
@ExceptionHandler(Exception.class)
public Result doException(Exception ex){
System.out.println("异常!!! 发现一个异常!!!");
return new Result("异常错误",-999,"异常!!! 发现一个异常!!!");
}
}
启动运行程序,测试

至此,就算后台执行的过程中抛出异常,最终也能按照我们和前端约定好的格式返回给前端。
| 名称 | @RestControllerAdvice |
|---|---|
| 类型 | 类注解 |
| 位置 | Rest风格开发的控制器增强类定义上方 |
| 作用 | 为Rest风格开发的控制器类做增强 |
**说明:**此注解自带@ResponseBody注解与@Component注解,具备对应的功能

| 名称 | @ExceptionHandler |
|---|---|
| 类型 | 方法注解 |
| 位置 | 专用于异常处理的控制器方法上方 |
| 作用 | 设置指定异常的处理方案,功能等同于控制器方法, 出现异常后终止原始控制器执行,并转入当前方法执行 |
**说明:**此类方法可以根据处理的异常不同,制作多个方法分别处理对应的异常
异常处理器我们已经能够使用了,那么在咱们的项目中该如何来处理异常呢?
因为异常的种类有很多,如果每一个异常都对应一个@ExceptionHandler,那得写多少个方法来处理各自的异常,所以我们在处理异常之前,需要对异常进行一个分类:
业务异常(BusinessException)
规范的用户行为产生的异常
用户在页面输入内容的时候未按照指定格式进行数据填写,如在用户名输入的是null

不规范的用户行为操作产生的异常
如用户故意传递错误数据

系统异常(SystemException)
其他异常(Exception)
编程人员未预期到的异常,如:用到的文件不存在

将异常分类以后,针对不同类型的异常,要提供具体的解决方案:
思路:
1.先通过自定义异常,完成BusinessException和SystemException的定义
2.将其他异常包装成自定义异常类型
3.在异常处理器类中对不同的异常进行处理
//自定义异常处理器,用于封装异常信息,对异常进行分类
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属性的原因是为了更好的区分异常是来自哪个业务的假如在UserServiceImpl的findByUserId方法抛异常了,该如何来包装呢?
public User findByUserId(Integer userId) {
//模拟业务异常,包装成自定义异常
if(userId == 10){
throw new BusinessException(ResultCode.BUSINESS_ERR,"请按要求请求参数!");
}
//模拟系统异常,将可能出现的异常进行包装,转换成自定义异常
try{
int i = 20/0;
}catch (Exception e){
throw new SystemException(ResultCode.SYSTEM_TIMEOUT_ERR,"服务器访问超时,请重试!",e);
}
return userDao.findByUserId(userId);
}
具体的包装方式有:
try{}catch(){}在catch中重新throw我们自定义异常即可。上面为了使code看着更专业些,我们在Code类中再新增需要的属性
//状态码
public class ResultCode {
// 成功 状态
public static final Integer SAVE_SUCCES = 200; //保存成功
public static final Integer UPDATE_SUCCES = 200; //修改成功
public static final Integer DELETE_SUCCES = 200; //删除成功
public static final Integer SELECT_SUCCES = 200; //查询成功
// 失败
public static final Integer SAVE_FAIL = 2001;//保存失败
public static final Integer UPDATE_FAIL = 2002;//修改失败
public static final Integer DELETE_FAIL = 2003;//删除失败
public static final Integer SELECT_FAIL = 2004;//查询失败
// 其他 ......
public static final Integer BUSINESS_ERR = -999;
public static final Integer SYSTEM_TIMEOUT_ERR = -888;
}
//@RestControllerAdvice用于标识当前类为REST风格对应的异常处理器
@RestControllerAdvice
public class ExceptionAdvice {
//除了自定义的异常处理器,保留对Exception类型的异常处理,用于处理非预期的异常
// @ExceptionHandler(Exception.class)
// public Result doException(Exception ex){
// System.out.println("异常!!! 发现一个异常!!!");
// return new Result("异常错误",-999,"异常!!! 发现一个异常!!!");
// }
//@ExceptionHandler用于设置当前处理器类对应的异常类型
@ExceptionHandler(SystemException.class)
public Result doSystemException(SystemException ex){
//记录日志
//发送消息给运维
//发送邮件给开发人员,ex对象发送给开发人员
return new Result(ex.getCode(),null,ex.getMessage());
}
@ExceptionHandler(BusinessException.class)
public Result doBusinessException(BusinessException ex){
return new Result(ex.getCode(),null,ex.getMessage());
}
//除了自定义的异常处理器,保留对Exception类型的异常处理,用于处理非预期的异常
@ExceptionHandler(Exception.class)
public Result doOtherException(Exception ex){
//记录日志
//发送消息给运维
//发送邮件给开发人员,ex对象发送给开发人员
return new Result(ResultCode.SYSTEM_UNKNOW_ERR,null,"系统繁忙,请稍后再试!");
}
}
根据userId查询,
如果传入的参数为10,会报BusinessException

如果传入的是其他参数,会报SystemException

对于异常我们就已经处理完成了,不管后台哪一层抛出异常,都会以我们与前端约定好的方式进行返回,前端只需要把信息获取到,根据返回的正确与否来展示不同的内容即可。
小结
以后项目中的异常处理方式为:
