文章目录
- @ResponseBody
- @ControllerAdvice
- 最终的异常处理方式
异常的处理分两类
编程式处理:也就是我们的try-catch
声明式处理:使用注解处理
@ResponseBody
/**
* 测试声明式异常处理
*/
@RestController
public class HelloController {
//编程式的异常处理;
//如果大量业务都需要加异常处理代码的话,会很麻烦
// try {
// //执行业务
//
// }catch (Exception e){
// return R.error(100,"执行异常");
// }
@GetMapping("/hello")
public R hello(@RequestParam(value = "i",defaultValue = "0") Integer i) throws FileNotFoundException {
int j = 10 / i;
// FileInputStream inputStream = new FileInputStream("D:\\123.txt");
String s = null;
s.length();
return R.ok(j);
}
/**
* 1、如果Controller本类出现异常,会自动在本类中找有没有@ExceptionHandler标注的方法,
* 如果有,执行这个方法,它的返回值,就是客户端收到的结果
* 如果发生异常,多个都能处理,就精确优先
* @return
*/
@ResponseBody
@ExceptionHandler(ArithmeticException.class)
public R handleArithmeticException(ArithmeticException ex){
System.out.println("【本类】 - ArithmeticException 异常处理");
return R.error(100,"执行异常:" + ex.getMessage());
}
@ExceptionHandler(FileNotFoundException.class)
public R handleException(FileNotFoundException ex){
System.out.println("【本类】 - FileNotFoundException 异常处理");
return R.error(300,"文件未找到异常:" + ex.getMessage());
}
@ExceptionHandler(Throwable.class)
public R handleException02(Throwable ex){
System.out.println("【本类】 - Throwable 异常处理");
return R.error(500,"其他异常:" + ex.getMessage());
}
}
@ExceptionHandler只能处理本类的
所以其他类的报错怎么办呢?
使用@ControllerAdvice
@ControllerAdvice
// 全局异常处理器
//@ResponseBody // 结果还是以JSON的形式写出去
//@ControllerAdvice //告诉SpringMVC,这个组件是专门负责进行全局异常处理的
@RestControllerAdvice // 合成注解
public class GlobalExceptionHandler {
/**
* 如果出现了异常:本类和全局都不能处理,
* SpringBoot底层对SpringMVC有兜底处理机制;自适应处理(浏览器响应页面、移动端会响应json)
* 最佳实践:我们编写全局异常处理器,处理所有异常
* <p>
* 前端关心异常状态,后端正确业务流程。
* 推荐:后端只编写正确的业务逻辑,如果出现业务问题,后端通过抛异常的方式提前中断业务逻辑。前端感知异常;
* <p>
* 异常处理:
* 1、
*
* @param e
* @return
*/
@ExceptionHandler(ArithmeticException.class)
public R error(ArithmeticException e) {
System.out.println("【全局】 - ArithmeticException 处理");
return R.error(500, e.getMessage());
}
@ExceptionHandler(BizException.class)
public R handleBizException(BizException e) {
Integer code = e.getCode();
String msg = e.getMsg();
return R.error(code, msg);
}
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public R methodArgumentNotValidException(MethodArgumentNotValidException ex) {
//1、result 中封装了所有错误信息
BindingResult result = ex.getBindingResult();
List<FieldError> errors = result.getFieldErrors();
Map<String, String> map = new HashMap<>();
for (FieldError error : errors) {
String field = error.getField();
String message = error.getDefaultMessage();
map.put(field, message);
}
return R.error(500, "参数错误", map);
}
// 最终的兜底
@ExceptionHandler(Throwable.class)
public R error(Throwable e) {
System.out.println("【全局】 - Exception处理" + e.getClass());
return R.error(500, e.getMessage());
}
}
异常处理优先级
- 本类 > 全局
- 精确 > 模糊
如果出现了异常,本类和全局都不能处理,SpringMVC会兜底处理机制: 自适应处理(什么样的客户端返回什么,要是浏览器就返回一个错误页面,要是客户端,比如Postman,返回json)
实际上做项目的时候:我们编写全局异常处理器,处理所有异常
最终的异常处理方式
前端关心异常状态
后端正确业务流程
推荐:后端只编写正确的业务逻辑,如果出现业务问题,后端通过抛异常的方式提前中断业务逻辑。前端感知异常;
定义一个业务异常
/**
* 业务异常
* 大型系统出现以下异常:异常处理文档,固化
* 1、订单 1xxxx
* 10001 订单已关闭
* 10002 订单不存在
* 10003 订单超时
* .....
* 2、商品 2xxxx
* 20001 商品已下架
* 20002 商品已售完
* 20003 商品库存不足
* ......
* 3、用户
* 30001 用户已注册
* 30002 用户已登录
* 30003 用户已注销
* 30004 用户已过期
*
* 4、支付
* 40001 支付失败
* 40002 余额不足
* 40003 支付渠道异常
* 40004 支付超时
*
* 5、物流
* 50001 物流状态错误
* 50002 新疆得加钱
* 50003 物流异常
* 50004 物流超时
*
* 异常处理的最终方式:
* 1、必须有业务异常类:BizException
* 2、必须有异常枚举类:BizExceptionEnume 列举项目中每个模块将会出现的所有异常情况
* 3、编写业务代码的时候,只需要编写正确逻辑,如果出现预期的问题,需要以抛异常的方式中断逻辑并通知上层。
* 4、全局异常处理器:GlobalExceptionHandler; 处理所有异常,返回给前端约定的json数据与错误码
*/
@Data
public class BizException extends RuntimeException {
private Integer code; //业务异常码
private String msg; //业务异常信息
public BizException(Integer code, String message) {
super(message);
this.code = code;
this.msg = message;
}
public BizException(BizExceptionEnume exceptionEnume) {
super(exceptionEnume.getMsg());
this.code = exceptionEnume.getCode();
this.msg = exceptionEnume.getMsg();
}
}
为了便于管理,我们把所有的异常码和异常信息写一个枚举类。
public enum BizExceptionEnume {
// ORDER_xxx:订单模块相关异常
// PRODUCT_xxx:商品模块相关异常
// 动态扩充.....
ORDER_CLOSED(10001, "订单已关闭"),
ORDER_NOT_EXIST(10002, "订单不存在"),
ORDER_TIMEOUT(10003, "订单超时"),
PRODUCT_STOCK_NOT_ENOUGH(20003, "库存不足"),
PRODUCT_HAS_SOLD(20002, "商品已售完"),
PRODUCT_HAS_CLOSED(20001, "商品已下架");
@Getter
private Integer code;
@Getter
private String msg;
private BizExceptionEnume(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
}
然后在我们的全局异常处理器中处理我们的业务异常
@ExceptionHandler(BizException.class)
public R handleBizException(BizException e) {
Integer code = e.getCode();
String msg = e.getMsg();
return R.error(code, msg);
}
在业务中就可以这样用了。
@Override
public void updateEmp(Employee employee) {
//防null处理。考虑到service是被controller调用的;
//controller层传过来的employee 的某些属性可能为null,所以先处理一下
//怎么处理?
Long id = employee.getId();
if(id == null){ //页面没有带id
//中断的业务的时候,必须让上层及以上的链路知道中断原因。推荐抛出业务异常
throw new BizException(BizExceptionEnume.ORDER_CLOSED);
}
……