Skip to content

Spring MVC 全局异常处理

本文主要介绍如何使用Spring MVC提供的工具进行全局异常处理。参考内容:

1. 环境搭建

首先准备测试的Controller:

java
@RestController
public class TestController {

    @GetMapping("/calc")
    public R calc(@RequestParam("num1") int num1,
                  @RequestParam("num2") int num2)
    {
        int result = num1 / num2;

        return R.ok(result);
    }
}
java
@Data
@AllArgsConstructor
@NoArgsConstructor
public class R<T> {
    private int code;
    private String msg;
    @JsonRawValue
    private T data;

    public static <T> R ok(T data){
        return new R<T>(1, "success", data);
    }

    public static <T> R error(String msg, T data){
        return new R<T>(-1, msg, data);
    }
  
    public static <T> R error(int code, String msg, T data){
       return new R<T>(code, msg, data);
    }
}

启动应用,访问/calc接口,效果如下:

![image-20241219184631127](./assets/Spring MVC 全局异常处理/image-20241219184631127.png)

但是,在我们的程序中没有处理除零异常,如果num2传零,那么会有如下报错:

![image-20241219184730894](./assets/Spring MVC 全局异常处理/image-20241219184730894.png)

可以看到如果程序出现异常,Spring MVC有一个默认的异常返回值。

2. 类级别的异常处理器

我们可以在Controller里面提供异常处理器(由@ExceptionHandler标注的方法),当该类中的接口抛出异常时,会由该方法接受异常并进行处理和返回。

java
@RestController
public class TestController {

    @GetMapping("/calc")
    public R calc(@RequestParam("num1") int num1,
                  @RequestParam("num2") int num2)
    {
        int result = num1 / num2;

        return R.ok(result);
    }

    @ExceptionHandler(ArithmeticException.class)
    private R handleArithmeticException(ArithmeticException e){
        return R.error("数学运算异常:" + e.getMessage(), e);
    }

    @ExceptionHandler(Throwable.class)
    private R handleException(Throwable e){
        return R.error(e.getMessage(), e);
    }
}

测试效果如下:

![image-20241219185427120](./assets/Spring MVC 全局异常处理/image-20241219185427120.png)

@ExceptionHandler注解中,我们可以指定异常的类型(可多个),只有程序抛出特定的异常后才进入该方法执行。同时,我们也可以提供一个默认的异常处理器@ExceptionHandler(Throwable.class),用于处理其他未指定的异常。

3.全局异常处理器

如果在一个大项目中,为每一个Controller指定异常处理器,那会是一项非常重复的工作。所以我们可以配置全局异常处理器(将Controller中的异常处理方法移到全局异常处理器中):

java
@ControllerAdvice
@ResponseBody
public class GlobalExceptionHandler {

    @ExceptionHandler(ArithmeticException.class)
    private R handleArithmeticException(ArithmeticException e){
        return R.error("数学运算异常:" + e.getMessage(), e);
    }

    @ExceptionHandler(Throwable.class)
    private R handleException(Throwable e){
        return R.error(e.getMessage(), e);
    }
}
  • 使用@ControllerAdvice表示该类是用于增强Controller的;
  • 注意要使用@ResponseBody

4. 异常处理最终方式

  1. 定义异常枚举类:在大项目中,我们可能存在多个模块,需要在枚举类中按模块划分异常;
  2. 定义业务异常;
  3. 编写业务代码时,只需要编写正确的逻辑,如果出现预期的问题,需要以抛异常的方式中断逻辑并通知上层;
  4. 通过全局异常处理器处理异常;
java
public enum BizExceptionEnum {

    // 商品模块异常
    GOODS_NOT_FOUND(1001, "商品不存在"),
    GOODS_OUT_OF_STOCK(1002,"库存不足"),

    // 订单模块异常
    ORDRE_TIMEOUT(2001, "订单超时"),
    ORDER_NOT_FOUND(2002, "订单不存在"),

    // 用户模块异常
    USER_NOT_FOUND(3001,"用户不存在"),

    // 物流模块异常
    LOGISTICS_OVER_WEIGHT(4001,"超重");

    @Getter
    private int code;
    @Getter
    private String msg;

    private BizExceptionEnum(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }
}
java
@Getter
public class BizException extends Throwable{
    private int code;
    private String message;

    public BizException(int code, String message) {
        this.code = code;
        this.message = message;
    }

    public BizException(BizExceptionEnum bizExceptionEnum) {
        this.code = bizExceptionEnum.getCode();
        this.message = bizExceptionEnum.getMsg();
    }
}
java
@ExceptionHandler(BizException.class)
private R handleBizException(BizException bizException){
    return R.error(bizException.getCode(), "业务异常:" + bizException.getMessage(), bizException);
}

之后,在我们的业务逻辑中,可以按照如下方式抛出异常:

java
public Goods updateGoodsById(Goods goods) throws BizException {
    // 查询Goods
    Goods goods = goodsMapper.getGoodsById(goods.getId());
    if(goods == null){
        // 未查到抛异常,结束业务逻辑
        throw new BizException(BizExceptionEnum.GOODS_NOT_FOUND);
    }

    // 查到了,执行修改商品的逻辑
    ...

}