Spring Boot 中的异常处理机制
在项目中我们可以通过自定义异常类 QLException、全局异常处理器 GlobalExceptionHandler 和响应数据封装类 ResSerialization,我们实现了一个统一的异常处理机制。这种机制不仅提高了代码的整洁度和可维护性,还确保了系统的健壮性和用户体验的一致性。日志配置的合理使用,也为后续的故障排查提供了有力支持。
1. 自定义异常类 QLException
代码内容
@Data
public class QLException extends RuntimeException {
private String msg;
private int code = 1001;
private int status = 404;
public QLException(BaseExecptionEnum execptionEnum) {
super(execptionEnum.getValue());
this.status = execptionEnum.getStatus();
this.code = execptionEnum.getCode();
this.msg = execptionEnum.getValue();
}
public QLException(String msg) {
super(msg);
this.msg = msg;
this.code = QLHttpStatus.NOT_FOUND.value();
}
public QLException(String msg, int code, int status) {
super(msg);
this.msg = msg;
this.code = code;
this.status = status;
}
public QLException(BaseExecptionEnum execptionEnum, Throwable err) {
super(execptionEnum.getValue(), err);
this.status = execptionEnum.getStatus();
this.code = execptionEnum.getCode();
this.msg = execptionEnum.getValue();
}
}
逐行解析
-
@Data
:- 这是 Lombok 注解,自动生成
getter
、setter
、toString
等方法,减少样板代码。
@Data
- 这是 Lombok 注解,自动生成
-
public class QLException extends RuntimeException
:- 定义了一个自定义异常类
QLException
,继承自RuntimeException
,因此它是一个运行时异常,不需要在方法签名中声明。
public class QLException extends RuntimeException {
- 定义了一个自定义异常类
-
private String msg;
:- 定义一个字段
msg
,用于存储异常的错误信息。
private String msg;
- 定义一个字段
-
private int code = 1001;
:- 定义一个字段
code
,用于存储业务状态码,默认值为1001
。
private int code = 1001;
- 定义一个字段
-
private int status = 404;
:- 定义一个字段
status
,用于存储 HTTP 状态码,默认值为404
。
private int status = 404;
- 定义一个字段
-
public QLException(BaseExecptionEnum execptionEnum)
:- 构造方法,接收一个
BaseExecptionEnum
枚举对象。 super(execptionEnum.getValue());
: 调用父类RuntimeException
的构造方法,传入枚举中的错误信息。this.status = execptionEnum.getStatus();
: 从枚举中获取 HTTP 状态码并赋值给status
字段。this.code = execptionEnum.getCode();
: 从枚举中获取业务状态码并赋值给code
字段。this.msg = execptionEnum.getValue();
: 从枚举中获取错误信息并赋值给msg
字段。
public QLException(BaseExecptionEnum execptionEnum) { super(execptionEnum.getValue()); this.status = execptionEnum.getStatus(); this.code = execptionEnum.getCode(); this.msg = execptionEnum.getValue(); }
- 构造方法,接收一个
-
public QLException(String msg)
:- 构造方法,接收一个错误信息字符串。
super(msg);
: 调用父类RuntimeException
的构造方法,传入错误信息。this.msg = msg;
: 将传入的错误信息赋值给msg
字段。this.code = QLHttpStatus.NOT_FOUND.value();
: 设置默认的业务状态码为404
。
public QLException(String msg) { super(msg); this.msg = msg; this.code = QLHttpStatus.NOT_FOUND.value(); }
-
public QLException(String msg, int code, int status)
:- 构造方法,接收错误信息、业务状态码和 HTTP 状态码。
super(msg);
: 调用父类RuntimeException
的构造方法,传入错误信息。this.msg = msg;
: 将传入的错误信息赋值给msg
字段。this.code = code;
: 将传入的业务状态码赋值给code
字段。this.status = status;
: 将传入的 HTTP 状态码赋值给status
字段。
public QLException(String msg, int code, int status) { super(msg); this.msg = msg; this.code = code; this.status = status; }
-
public QLException(BaseExecptionEnum execptionEnum, Throwable err)
:- 构造方法,接收一个
BaseExecptionEnum
枚举对象和一个Throwable
异常对象。 super(execptionEnum.getValue(), err);
: 调用父类RuntimeException
的构造方法,传入枚举中的错误信息和原始异常。this.status = execptionEnum.getStatus();
: 从枚举中获取 HTTP 状态码并赋值给status
字段。this.code = execptionEnum.getCode();
: 从枚举中获取业务状态码并赋值给code
字段。this.msg = execptionEnum.getValue();
: 从枚举中获取错误信息并赋值给msg
字段。
public QLException(BaseExecptionEnum execptionEnum, Throwable err) { super(execptionEnum.getValue(), err); this.status = execptionEnum.getStatus(); this.code = execptionEnum.getCode(); this.msg = execptionEnum.getValue(); }
- 构造方法,接收一个
2. 全局异常处理器 GlobalExceptionHandler
代码内容
@RestControllerAdvice("com.qingluo.pay")
@Slf4j
public class GlobalExceptionHandler {
@ExceptionHandler(HttpMediaTypeNotAcceptableException.class)
public Serializable handleHttpMediaTypeNotAcceptableException(HttpMediaTypeNotAcceptableException e) {
log.error("不支持的媒体类型异常 --> {} ", e);
return ResSerialization.error(HttpStatus.UNSUPPORTED_MEDIA_TYPE.value(), "不支持的媒体类型");
}
@ExceptionHandler
public Serializable handle(IllegalArgumentException e) {
if (ObjectUtil.isNotEmpty(e.getMessage())) {
log.error("Assert参数断言异常 --> {} ", e);
}
return ResSerialization.error(HttpStatus.BAD_REQUEST.value(), "参数异常");
}
@ExceptionHandler
public Serializable handle(ValidationException e) {
List<String> errors = null;
if (e instanceof ConstraintViolationException) {
ConstraintViolationException exs = (ConstraintViolationException) e;
Set<ConstraintViolation<?>> violations = exs.getConstraintViolations();
errors = violations.stream()
.map(ConstraintViolation::getMessage).collect(Collectors.toList());
}
if (ObjectUtil.isNotEmpty(e.getCause())) {
log.error("参数校验失败异常 -> {} ", e);
}
return ResSerialization.error(HttpStatus.BAD_REQUEST.value(), errors.toString());
}
@ExceptionHandler
public Serializable handle(QLException e) {
if (ObjectUtil.isNotEmpty(e.getStatus())) {
log.error("自定义异常 --> {} ", e.getMsg());
}
return ResSerialization.error(e.getCode(), e.getMessage());
}
@ExceptionHandler
public Serializable handle(Exception e) {
if (ObjectUtil.isNotEmpty(e.getCause())) {
log.error("未知异常 --> {} ", e.getMessage());
}
return ResSerialization.error(HttpStatus.BAD_REQUEST.value(), e.getMessage());
}
}
逐行解析
-
@RestControllerAdvice("com.qingluo.pay")
:- 这是一个 Spring 注解,表示这是一个全局异常处理器,处理
com.qingluo.pay
包下的所有控制器抛出的异常。
@RestControllerAdvice("com.qingluo.pay")
- 这是一个 Spring 注解,表示这是一个全局异常处理器,处理
-
@Slf4j
:- 这是 Lombok 注解,自动生成日志对象
log
,用于记录日志。
@Slf4j
- 这是 Lombok 注解,自动生成日志对象
-
@ExceptionHandler(HttpMediaTypeNotAcceptableException.class)
:- 这是一个异常处理方法,专门处理
HttpMediaTypeNotAcceptableException
异常。 log.error("不支持的媒体类型异常 --> {} ", e);
: 记录异常日志。return ResSerialization.error(HttpStatus.UNSUPPORTED_MEDIA_TYPE.value(), "不支持的媒体类型");
: 返回一个错误响应,HTTP 状态码为415
,错误信息为 “不支持的媒体类型”。
@ExceptionHandler(HttpMediaTypeNotAcceptableException.class) public Serializable handleHttpMediaTypeNotAcceptableException(HttpMediaTypeNotAcceptableException e) { log.error("不支持的媒体类型异常 --> {} ", e); return ResSerialization.error(HttpStatus.UNSUPPORTED_MEDIA_TYPE.value(), "不支持的媒体类型"); }
- 这是一个异常处理方法,专门处理
-
@ExceptionHandler
:- 这是一个通用的异常处理方法,处理
IllegalArgumentException
异常。 if (ObjectUtil.isNotEmpty(e.getMessage())) { ... }
: 如果异常信息不为空,则记录日志。return ResSerialization.error(HttpStatus.BAD_REQUEST.value(), "参数异常");
: 返回一个错误响应,HTTP 状态码为400
,错误信息为 “参数异常”。
@ExceptionHandler public Serializable handle(IllegalArgumentException e) { if (ObjectUtil.isNotEmpty(e.getMessage())) { log.error("Assert参数断言异常 --> {} ", e); } return ResSerialization.error(HttpStatus.BAD_REQUEST.value(), "参数异常"); }
- 这是一个通用的异常处理方法,处理
-
@ExceptionHandler
:- 这是一个通用的异常处理方法,处理
ValidationException
异常。 List<String> errors = null;
: 定义一个列表,用于存储校验失败的错误信息。if (e instanceof ConstraintViolationException) { ... }
: 如果异常是ConstraintViolationException
类型,则提取校验失败的具体信息。return ResSerialization.error(HttpStatus.BAD_REQUEST.value(), errors.toString());
: 返回一个错误响应,HTTP 状态码为400
,错误信息为校验失败的具体信息。
@ExceptionHandler public Serializable handle(ValidationException e) { List<String> errors = null; if (e instanceof ConstraintViolationException) { ConstraintViolationException exs = (ConstraintViolationException) e; Set<ConstraintViolation<?>> violations = exs.getConstraintViolations(); errors = violations.stream() .map(ConstraintViolation::getMessage).collect(Collectors.toList()); } if (ObjectUtil.isNotEmpty(e.getCause())) { log.error("参数校验失败异常 -> {} ", e); } return ResSerialization.error(HttpStatus.BAD_REQUEST.value(), errors.toString()); }
- 这是一个通用的异常处理方法,处理
-
@ExceptionHandler
:- 这是一个通用的异常处理方法,处理
QLException
异常。 if (ObjectUtil.isNotEmpty(e.getStatus())) { ... }
: 如果异常的状态码不为空,则记录日志。return ResSerialization.error(e.getCode(), e.getMessage());
: 返回一个错误响应,业务状态码和错误信息来自异常对象。
@ExceptionHandler public Serializable handle(QLException e) { if (ObjectUtil.isNotEmpty(e.getStatus())) { log.error("自定义异常 --> {} ", e.getMsg()); } return ResSerialization.error(e.getCode(), e.getMessage()); }
- 这是一个通用的异常处理方法,处理
-
@ExceptionHandler
:- 这是一个通用的异常处理方法,处理所有未知异常。
if (ObjectUtil.isNotEmpty(e.getCause())) { ... }
: 如果异常的原始原因不为空,则记录日志。return ResSerialization.error(HttpStatus.BAD_REQUEST.value(), e.getMessage());
: 返回一个错误响应,HTTP 状态码为400
,错误信息为异常的具体信息。
@ExceptionHandler public Serializable handle(Exception e) { if (ObjectUtil.isNotEmpty(e.getCause())) { log.error("未知异常 --> {} ", e.getMessage()); } return ResSerialization.error(HttpStatus.BAD_REQUEST.value(), e.getMessage()); }
3. 总结
通过逐行解析自定义异常类 QLException
和全局异常处理器 GlobalExceptionHandler
,我们深入理解了 Spring Boot 中异常处理的实现细节。这种机制不仅提高了代码的可维护性,还为用户提供了友好的错误提示。希望本文对您有所帮助!