目录
前言
在实际项目中,异常处理和接口响应规范化是保障系统健壮性和可维护性的关键。本文将手把手教你用SpringBoot实现:
-
全局异常捕获:通过
@ControllerAdvice
统一处理所有异常 -
自定义业务异常:优雅区分系统错误与业务错误
-
统一响应体:标准化接口返回格式,告别杂乱响应
一、@ControllerAdvice实现全局异常捕获
1.1 什么是@ControllerAdvice?
@ControllerAdvice
是Spring提供的全局增强控制器注解,可结合@ExceptionHandler
捕获所有Controller层抛出的异常。
1.2 创建全局异常处理器
@RestControllerAdvice
public class GlobalExceptionHandler {
// 处理系统异常(兜底处理)
@ExceptionHandler(Exception.class)
public ResultVO handleException(Exception e) {
log.error("系统异常: {}", e.getMessage());
return ResultVO.error(500, "系统繁忙,请稍后再试");
}
// 处理自定义业务异常
@ExceptionHandler(BusinessException.class)
public ResultVO handleBusinessException(BusinessException e) {
log.warn("业务异常: {}", e.getMessage());
return ResultVO.error(e.getCode(), e.getMessage());
}
// 处理参数校验异常(需配合@Validated使用)
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResultVO handleValidException(MethodArgumentNotValidException e) {
String message = e.getBindingResult().getFieldError().getDefaultMessage();
return ResultVO.error(400, message);
}
}
关键注解说明:
-
@RestControllerAdvice
=@ControllerAdvice
+@ResponseBody
-
@ExceptionHandler
:指定要捕获的异常类型
二、自定义业务异常类
2.1 为什么需要自定义异常?
-
区分系统异常与业务异常(如订单不存在、库存不足)
-
携带特定错误码,方便前端处理
-
统一异常信息格式
2.2 实现自定义异常
public class BusinessException extends RuntimeException {
private final int code;
public BusinessException(int code, String message) {
super(message);
this.code = code;
}
public int getCode() {
return code;
}
}
2.3 异常使用示例
在Service层抛出业务异常:
public User getUserById(Long id) {
User user = userRepository.findById(id);
if (user == null) {
throw new BusinessException(1001, "用户不存在");
}
return user;
}
三、统一响应体设计(ResultVO)
3.1 响应体结构设计
标准化响应应包含:状态码、消息、数据三要素。
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ResultVO<T> {
private int code; // 状态码(200=成功)
private String msg; // 提示信息
private T data; // 响应数据
// 成功响应(无数据)
public static <T> ResultVO<T> success() {
return new ResultVO<>(200, "success", null);
}
// 成功响应(带数据)
public static <T> ResultVO<T> success(T data) {
return new ResultVO<>(200, "success", data);
}
// 错误响应
public static <T> ResultVO<T> error(int code, String msg) {
return new ResultVO<>(code, msg, null);
}
}
3.2 Controller层统一返回
修改Controller方法返回值类型为ResultVO
:
@GetMapping("/user/{id}")
public ResultVO<User> getUser(@PathVariable Long id) {
User user = userService.getUserById(id);
return ResultVO.success(user);
}
3.3 响应示例
成功响应:
{
"code": 200,
"msg": "success",
"data": {
"id": 1,
"name": "张三"
}
}
异常响应:
{
"code": 1001,
"msg": "用户不存在",
"data": null
}
四、进阶优化技巧
4.1 状态码枚举管理
使用枚举统一管理错误码,避免魔法数字:
public enum ResultCode {
SUCCESS(200, "操作成功"),
USER_NOT_FOUND(1001, "用户不存在"),
PARAM_INVALID(400, "参数错误");
private final int code;
private final String msg;
// 构造方法、getter省略
}
修改ResultVO的error方法:
public static <T> ResultVO<T> error(ResultCode resultCode) {
return new ResultVO<>(resultCode.getCode(), resultCode.getMsg(), null);
}
4.2 自动记录异常日志
通过AOP在异常发生时自动记录日志(可选):
@Aspect
@Component
public class ExceptionLogAspect {
@AfterThrowing(pointcut = "execution(* com.example..*.*(..))", throwing = "e")
public void logException(JoinPoint joinPoint, Exception e) {
String method = joinPoint.getSignature().getName();
String className = joinPoint.getTarget().getClass().getName();
log.error("类 {} 的方法 {} 发生异常: {}", className, method, e.getMessage());
}
}
4.3 处理第三方异常(如Feign调用异常)
通过实现ErrorDecoder
自定义Feign异常解析:
public class FeignErrorDecoder implements ErrorDecoder {
@Override
public Exception decode(String methodKey, Response response) {
String message = "Feign调用异常: " + response.status();
return new BusinessException(5001, message);
}
}
五、常见问题与解决方案
5.1 异常处理不生效?
-
检查全局异常处理器是否被Spring扫描到(是否在启动类同级或子包)
-
确认Controller方法没有用
try-catch
局部捕获
5.2 如何区分不同异常响应状态码?
-
系统异常统一返回500系列状态码
-
业务异常使用400或自定义业务码(如1001)
-
参数校验异常返回400
5.3 如何处理文件上传大小限制异常?
在application.properties
中配置:
spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=10MB
捕获MaxUploadSizeExceededException
:
@ExceptionHandler(MaxUploadSizeExceededException.class)
public ResultVO handleFileSizeLimitExceeded() {
return ResultVO.error(400, "文件大小超过限制");
}