Spring:使用自定义异常

Spring:使用自定义异常

为什么要使用自定义的异常呢,难道说Java自带的异常不够好么?

因为后端的最终目的是为了和前端进行交互,如果某个程序产生了异常,他只会在后台抛出堆栈信息,而不是以json等形式返回给前端,因此,我们需要一个捕捉报错,并且处理报错信息,以json的形式返回给前端的一个逻辑。
这既能够让前端获取到报错信息,还不会因为异常导致后端结构暴露在外面

实例

1
2
3
4
5
6
7
public class BaseException extends Exception{  
public BaseException() {
}
public BaseException(String msg) {
super(msg);
}
}

我们定义了一个名为BaseException的异常,继承于Exception,[[Spring:整体思路讲解]]中很多逻辑都有一个throw new BaseException(“xxx错误信息”)的代码,表示这个异常被我用BaseException捕获到了,就不会产生系统异常的报错。
有一个异常还不够,因为报错有很多种类,比如数据库异常,redis服务异常等等,我们如何识别这些不同的报错信息呢。
因此我们需要一个全局异常处理器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

@ExceptionHandler(BaseException.class)
public Result<String> handleBaseException(BaseException ex) {
return Result.error(ex.getMessage());
}

@ExceptionHandler(DataIntegrityViolationException.class)
public Result<String> handleDBException(DataIntegrityViolationException ex) {
return Result.error("数据库错误:" + ex.getMessage());
}

@ExceptionHandler(Exception.class)
public Result<String> handleOtherException(Exception ex) {
if (ex.getMessage() != null && ex.getMessage().contains("6379")) {
log.error("Redis未启动!");
return Result.error("Redis服务未启动!");
}
log.error("未知异常:{}", ex.getMessage());
return Result.error("服务器内部错误");
}
}

@RestControllerAdvice的作用?

全局异常处理器 + 全局数据绑定 + 全局返回值处理器
定义好BaseException之后就需要一个全局异常处理器对这些异常进行处理

这里我们返回值统一采用了Result进行封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Data  
public class Result<T> implements Serializable {

private Integer code; //编码:1成功,0和其它数字为失败
private String msg; //错误信息
private T data; //数据

public static <T> Result<T> success() {
Result<T> result = new Result<T>();
result.code = 1;
return result;
}

public static <T> Result<T> success(T object) {
Result<T> result = new Result<T>();
result.data = object;
result.code = 1;
return result;
}

public static <T> Result<T> error(String msg) {
Result result = new Result();
result.msg = msg;
result.code = 0;
return result;
}

}

这样,即使有报错,也会使用BaseException进行抛出,抛出的异常会被GlobalExceptionHandler捕获到,然后

1
2
3
4
@ExceptionHandler  
public Result<String> exceptionHandler(BaseException ex) {
return Result.error(ex.getMessage());
}

使用这个方法统一返回
当然我们也可以捕获其他方法

1
2
3
4
@ExceptionHandler  
public Result<String> exceptionDBHandler(DataIntegrityViolationException ex) {
return Result.error(ex.getMessage());
}

也可以直接捕获Exception,然后对其处理

1
2
3
4
5
6
7
8
9
@ExceptionHandler  
public Result<String> exceptionHandler(Exception ex) {
if (ex.getMessage().contains("6379")){
log.error("Redis未启动");
return Result.error("Redis未启动");
}
log.error("异常信息:{}", ex.getMessage());
return Result.error(ex.getMessage());
}

比如以上代码,通过捕获异常中的内容,判断如果内容包含 6379 就添加一个错误日志 redis未启动,并且使用Result.error进行返回异常

因为产生异常之后,程序就就不会继续向下执行,因此直接走你写好的返回向前端返回。

实际用法

1
2
3
if (userMapper.findByUsername(userDTO.getUsername()) != null) {
throw new BaseException("用户名已存在");
}

这样前端就会接收到:

1
2
3
4
5
{
"code": 0,
"msg": "用户名已存在",
"data": null
}

这种不在方法中针对性写出的,而是独立于业务逻辑,集中对某些逻辑进行处理的思想被称为AOP
面相切面编程(挖坑

曾经代码的不足

1. Result泛型封装不止0和1,也可以加上状态码常量

  • 业务异常(400)
  • 权限异常(401)
  • 系统异常(500)
    或者单独定义一个通用常亮
1
2
3
4
5
6
public class ResultCode {
public static final int SUCCESS = 1;
public static final int FAIL = 0;
public static final int UNAUTHORIZED = 401;
public static final int SERVER_ERROR = 500;
}

2. 可以直接使用RuntimeException,运行时异常

我们继承的是Exception,这是一种受检异常,每次用的时候都需要 throws 或者 try-catch
而继承RuntimeException后则不用这么麻烦

1
2
3
4
5
public class BaseException extends RuntimeException {
public BaseException(String msg) {
super(msg);
}
}

Spring:使用自定义异常
https://www.zheep.top/posts/1585822579/
作者
西行寺岩羊
发布于
2025年5月7日
许可协议