
作为一名资深 Java 开发工程师,我在多个 Spring Boot 项目中都遇到过异常处理的痛点。特别是在团队协作开发时,每个人处理异常的方式千差万别,导致线上问题排查时常常陷入混乱。今天就结合 Spring Boot3 的新特性,跟大家系统分享一套可落地的统一异常处理方案。
为什么要重构异常处理逻辑?
在最近接手的一个遗留项目中,我发现了这样一段代码:
@PostMapping("/user")
public ResponseEntity<?> addUser(@RequestBody User user) {
try {
if (user.getName() == null) {
return ResponseEntity.badRequest().body("用户名不能为空");
}
User saved = userService.save(user);
return ResponseEntity.ok(saved);
} catch (SQLException e) {
log.error("数据库错误", e);
return ResponseEntity.status(500).body("保存失败");
} catch (Exception e) {
return ResponseEntity.status(500).body(e.getMessage());
}
}
这段代码暴露了三个典型问题:
- 重复编码:每个接口都要写 try-catch 和参数校验
- 响应格式不一致:有的返回字符串,有的返回对象
- 异常信息不规范:生产环境可能泄露敏感信息
我们团队在引入统一异常处理后,代码量减少了 35%,线上问题平均排查时间从 2 小时缩短到 15 分钟。
Spring Boot3 异常处理核心实现
自定义异常体系设计
在 Spring Boot3 中,推荐基于业务域设计异常体系。核心思路是:
- 基础异常类保存错误码和消息
- 业务异常继承基础异常
- 系统异常单独处理
// 基础异常类
public class BaseException extends RuntimeException {
private final int code;
private final String details; // 用于存储详细错误信息
public BaseException(int code, String message) {
this(code, message, null);
}
public BaseException(int code, String message, String details) {
super(message);
this.code = code;
this.details = details;
}
// getters
}
// 业务异常示例
public class UserNotFoundException extends BaseException {
// 推荐使用枚举管理错误码
public UserNotFoundException(Long userId) {
super(1001, "用户不存在", "userId: " + userId);
}
}
这里特别提醒:Spring Boot3 中对异常的序列化机制做了优化,自定义异常最好实现 Serializable 接口。
统一响应模型设计
一个规范的响应模型应该包含:
- 状态码(区分业务码和 HTTP 状态码)
- 提示消息
- 业务数据(成功时返回)
- 错误详情(失败时返回)
- 时间戳
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ApiResponse<T> {
private int code;
private String message;
private T data;
private String errorDetails;
private long timestamp;
// 成功响应
public static <T> ApiResponse<T> success(T data) {
ApiResponse<T> response = new ApiResponse<>();
response.code = 0;
response.message = "操作成功";
response.data = data;
response.timestamp = System.currentTimeMillis();
return response;
}
// 错误响应
public static ApiResponse<?> error(int code, String message, String details) {
ApiResponse<?> response = new ApiResponse<>();
response.code = code;
response.message = message;
response.errorDetails = details;
response.timestamp = System.currentTimeMillis();
return response;
}
}
3. 全局异常处理器实现
Spring Boot3 中依然使用 @ControllerAdvice 注解,但新增了对 ProblemDetail 的支持(RFC 7807 标准)。
@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
// 处理业务异常
@ExceptionHandler(BaseException.class)
public ResponseEntity<ApiResponse<?>> handleBaseException(BaseException e) {
log.warn("业务异常: [{}] {}", e.getCode(), e.getMessage(), e);
ApiResponse<?> response = ApiResponse.error(
e.getCode(),
e.getMessage(),
e.getDetails()
);
return ResponseEntity.status(getHttpStatus(e.getCode())).body(response);
}
// 处理参数校验异常
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ApiResponse<?>> handleValidationException(MethodArgumentNotValidException e) {
// 提取所有校验错误
List<String> errors = e.getBindingResult().getFieldErrors().stream()
.map(error -> error.getField() + ": " + error.getDefaultMessage())
.collect(Collectors.toList());
log.warn("参数校验失败: {}", errors);
ApiResponse<?> response = ApiResponse.error(
400,
"参数校验失败",
String.join("; ", errors)
);
return ResponseEntity.badRequest().body(response);
}
// 处理系统异常
@ExceptionHandler(Exception.class)
public ResponseEntity<ApiResponse<?>> handleSystemException(Exception e) {
log.error("系统异常", e); // 记录完整堆栈
// 生产环境隐藏具体错误信息
String message = isProduction() ? "系统繁忙,请稍后再试" : e.getMessage();
ApiResponse<?> response = ApiResponse.error(500, message, null);
return ResponseEntity.internalServerError().body(response);
}
// 根据业务码获取HTTP状态码
private HttpStatus getHttpStatus(int code) {
if (code >= 1000 && code < 2000) return HttpStatus.BAD_REQUEST;
if (code >= 2000 && code < 3000) return HttpStatus.UNAUTHORIZED;
return HttpStatus.INTERNAL_SERVER_ERROR;
}
// 判断是否生产环境
private boolean isProduction() {
return "prod".equals(environment.getActiveProfiles()[0]);
}
}
实战进阶技巧
异常码设计规范
推荐采用分层设计:
1xxx: 业务参数错误(400)
2xxx: 认证授权错误(401/403)
3xxx: 资源访问错误(404)
5xxx: 系统内部错误(500)
可以用枚举统一管理:
public enum ErrorCode {
USER_NOT_FOUND(1001, "用户不存在"),
PARAM_INVALID(1002, "参数无效"),
TOKEN_EXPIRED(2001, "令牌过期");
private final int code;
private final String message;
// 构造器和getter
}
整合 Spring Security 异常
在有安全框架的项目中,需要处理认证相关异常:
@ExceptionHandler(AccessDeniedException.class)
public ResponseEntity<ApiResponse<?>> handleAccessDeniedException(AccessDeniedException e) {
ApiResponse<?> response = ApiResponse.error(2003, "权限不足", null);
return ResponseEntity.status(403).body(response);
}
异常监控与告警
结合 Spring Boot Actuator 和 Prometheus:
@Aspect
@Component
public class ExceptionMetricsAspect {
private final MeterRegistry meterRegistry;
@AfterThrowing(pointcut = "execution(* com.example..*.*(..))", throwing = "e")
public void recordExceptionMetrics(Exception e) {
String exceptionType = e.getClass().getSimpleName();
meterRegistry.counter("app.exceptions.total", "type", exceptionType).increment();
// 严重异常触发告警
if (e instanceof SQLException || e instanceof IOException) {
// 发送告警通知
}
}
}
线上问题排查指南
异常日志规范:
- 业务异常:WARN 级别,记录错误码和消息
- 系统异常:ERROR 级别,记录完整堆栈
- 敏感操作:需记录用户 ID 和操作内容
排查流程:
- 根据响应的错误码定位业务模块
- 搜索日志中的errorDetails字段
- 系统异常查看完整堆栈的根源
常见问题解决:
- 异常未被捕获:检查是否是 checked exception
- 响应格式不对:确认 @ResponseBody 注解是否生效
- 错误码冲突:建立团队共享的错误码文档
完整项目结构
com.example.exception
├── base/
│ ├── BaseException.java // 基础异常类
│ └── ApiResponse.java // 统一响应模型
├── business/
│ ├── UserException.java // 用户相关异常
│ └── OrderException.java // 订单相关异常
├── handler/
│ ├── GlobalExceptionHandler.java // 全局异常处理器
│ └── ExceptionMetricsAspect.java // 异常监控切面
└── ErrorCode.java // 错误码枚举
这套方案已经在我们公司的三个 Spring Boot3 项目中落地,稳定性和可维护性都得到了验证。实际使用时,提议根据团队规模和业务复杂度进行适当调整。如果大家在实施过程中遇到具体问题,欢迎在评论区交流讨论。
© 版权声明
文章版权归作者所有,未经允许请勿转载。
相关文章
暂无评论...


