本文目录导读:

对于Java中的异常处理流程规整,核心目标是:可读性强、职责单一、错误信息不丢失、避免反模式。
以下是一套高内聚、低耦合的规整方案,结合了最佳实践、代码分层以及现代Java特性。
顶层设计原则
在写具体代码前,先确立几个原则:
- 异常是异常,不是业务逻辑:不要用
try-catch控制流程(如用NumberFormatException判断字符串是否为数字)。 - 早抛出,晚捕获:在发现错误的第一时间抛出异常;在能真正处理它的地方(通常是Service或Controller层)捕获。
- 拥抱Unchecked Exception:继承
RuntimeException(非受检异常),减少throws声明对上层调用的污染。 - 禁止吞食异常:
catch之后什么都不做,或者只打日志却继续返回错误结果。
规整的最佳实践模式
定义统一的异常类体系
不要到处用 new Exception("msg"),建立一个规范化的结构。
// 基础业务异常
public class BusinessException extends RuntimeException {
private final String code; // 错误码,用于前端或API调用方识别
private final String message;
public BusinessException(String code, String message) {
super(message); // 保留给父类用于StackTrace
this.code = code;
this.message = message;
}
public BusinessException(String code, String message, Throwable cause) {
super(message, cause);
this.code = code;
this.message = message;
}
// Getter...
}
使用“封箱”模式:要么返回结果,要么抛出异常
避免方法内部既有返回值又悄悄处理异常,导致调用方困惑。
// ❌ 反模式:返回null表示失败
public User findUser(Long id) {
try {
return userMapper.selectById(id);
} catch (DataAccessException e) {
log.error("查询用户失败", e);
return null; // 调用方拿到null后还要判断
}
}
// ✅ 规整做法:明确声明可能抛出的异常
public User findUserOrThrow(Long id) {
try {
User user = userMapper.selectById(id);
if (user == null) {
throw new BusinessException("USER_NOT_FOUND", "用户不存在: " + id);
}
return user;
} catch (DataAccessException e) {
// 转换异常类型,让上层统一处理
throw new BusinessException("DB_ERROR", "数据库查询失败", e);
}
}
分层异常处理规范
不同的层关注点不同,异常处理要分层负责。
| 层级 | 职责 | 异常处理方式 |
|---|---|---|
| Controller | 接收请求,返回响应 | 不处理具体业务异常,交给全局异常处理器 |
| Service | 核心业务逻辑 | 抛出 BusinessException 或 调用外部依赖的异常 |
| Repository/DAO | 数据访问 | 抛出 DataAccessException 或 PersistenceException |
| Infrastructure | 网络、IO、第三方 | 捕获底层异常,转换成业务层能理解的类型 |
@Service
public class OrderService {
public void createOrder(OrderDTO dto) {
// 1. 参数校验(最好前置)
if (dto.getAmount() < 0) {
throw new BusinessException("INVALID_AMOUNT", "金额不能为负");
}
// 2. 业务逻辑
try {
// 调用余额服务(可能网络超时)
boolean balanceResult = balanceClient.deduct(dto.getUserId(), dto.getAmount());
} catch (TimeoutException e) {
// Infrastructure层的异常 -> 转为BusinessException
throw new BusinessException("BALANCE_TIMEOUT", "余额服务调用超时", e);
}
// 3. 更多逻辑...
}
}
统一响应与全局异常处理(Spring Boot 实践)
这是“规整”最关键的一步。不要让每个Controller方法都写 try-catch。
定义统一的响应体
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ApiResponse<T> {
private boolean success;
private String code; // 业务码
private String message;
private T data;
public static <T> ApiResponse<T> ok(T data) {
return new ApiResponse<>(true, "SUCCESS", "操作成功", data);
}
public static <T> ApiResponse<T> error(String code, String message) {
return new ApiResponse<>(false, code, message, null);
}
}
全局异常处理器
@RestControllerAdvice
public class GlobalExceptionHandler {
// 处理所有自定义的业务异常
@ExceptionHandler(BusinessException.class)
@ResponseStatus(HttpStatus.OK) // 前端注意看 code,而非 HTTP 状态码
public ApiResponse<Void> handleBusinessException(BusinessException e) {
// 可以在这里记录错误日志,但不要影响返回
log.warn("业务异常: code={}, message={}", e.getCode(), e.getMessage());
return ApiResponse.error(e.getCode(), e.getMessage());
}
// 处理参数校验异常(@Valid)
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ApiResponse<Void> handleValidationException(MethodArgumentNotValidException e) {
String msg = e.getBindingResult().getFieldErrors().stream()
.map(error -> error.getField() + ": " + error.getDefaultMessage())
.collect(Collectors.joining(", "));
return ApiResponse.error("VALIDATION_ERROR", msg);
}
// 处理未预料的异常(兜底)
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ApiResponse<Void> handleUnknownException(Exception e) {
log.error("系统未知异常", e); // 需要记录详细堆栈
return ApiResponse.error("SYSTEM_ERROR", "服务器繁忙,请稍后重试");
}
}
代码级规整技巧
使用 Optional 代替 if (obj == null)
// ❌
public String getCity(User user) {
if (user != null && user.getAddress() != null) {
return user.getAddress().getCity();
}
return "未知";
}
// ✅
public String getCity(User user) {
return Optional.ofNullable(user)
.map(User::getAddress)
.map(Address::getCity)
.orElse("未知");
}
注意:Optional 不要用作方法参数类型,它只适合做返回值。
使用 try-with-resources 自动关闭资源
Java 7+ 特性,避免 finally 中手动关闭造成的异常丢失。
// ✅ 自动关闭 InputStream 和 SQL 连接
try (InputStream is = new FileInputStream("data.txt");
BufferedReader br = new BufferedReader(new InputStreamReader(is))) {
String line = br.readLine();
} catch (IOException e) {
log.error("文件读取失败", e);
}
异常信息要包含上下文
// ❌ 不好的信息
log.error("插入失败");
// ✅ 好的信息
log.error("插入用户失败, userId={}, userName={}, cause={}", user.getId(), user.getName(), e.getMessage());
避坑指南(必须避免的反模式)
| 反模式 | 问题 | 规整做法 |
|---|---|---|
| 捕获 Exception 却不处理 | 隐藏了Bug,系统静默失效 | 至少打日志,并明确抛出业务异常或返回错误 |
| 用异常控制流程 | 性能极差(Stack Trace开销) | 用 if-else 或 Optional 取代 |
| 捕获后 throw new Exception() | 丢失原始异常堆栈 | 用 throw new BusinessException(msg, e) 传入原异常 |
| 在 finally 中使用 return | 吞掉 try 块中的异常 | 不用在 finally 中 return,或使用 try-with-resources |
| 方法签名抛出大量受检异常 | 调用方被迫 caught 或 propagate | 统一转为非受检异常(RuntimeException) |
高级:使用 Result 模式(函数式风格)
如果不喜欢异常,或者需要显式展示所有可能结果(如CQRS、FP风格),可以使用 Result 类型(类似Vavr或Java官方计划中的Result),但大多数Java项目使用“全局异常处理”已经足够规整了。
// 当你需要在一个方法里既返回成功值,也返回失败信息,并且不想抛异常时
public sealed interface Result<T> permits Success, Failure {
static <T> Result<T> ok(T value) { ... }
static <T> Result<T> fail(String msg) { ... }
}
// 使用
Result<User> result = userService.findUser(1L);
User user = switch (result) {
case Success(var v) -> v;
case Failure(var m) -> throw new BusinessException("ERROR", m);
};
规整流程检查清单
- 定义清晰异常体系:
BusinessException+ 错误码。 - 分层治理:Controller 不处理业务异常(交给
@RestControllerAdvice);Service 不捕获底层异常(抛出去)。 - 全局统一:只有一个地方(
GlobalExceptionHandler)负责拼接ApiResponse。 - 日志规范:捕获未知异常必须记录完整堆栈;业务异常只记录必要信息。
- 禁止吞食:catch 后要么记录 + 抛出,要么转换为更具体的异常。
- 保持简单:能用
if-else解决,就不要用异常。
按照这套方案,你的Java异常处理流程会变得清晰、可维护,并且对团队协作非常友好。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播