15_Java 异常处理与资源管理
受检异常、业务错误、异常传播和 try-with-resources 资源关闭。
Java 异常处理与资源管理
学习目标:区分可恢复失败与程序错误,使用 try-with-resources 保证资源释放。
1. 异常的角色
受检异常(checked exception)要求调用方捕获或声明;运行时异常通常表示程序约束被违反或不适合强制每层声明的失败。分类不是“严重程度”排行,API 设计要让调用方知道如何处理。
- 在能补救、转换或添加业务语义的层捕获异常。
- 保留原始原因:
throw new ServiceException("保存失败", cause)。 - 日志通常在请求或任务边界记录一次,避免每层重复打印同一堆栈。
- HTTP 客户端看到稳定错误码和安全的说明,不能看到数据库连接串或内部堆栈。
2. 自动关闭资源
import java.io.BufferedReader;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
public class Main {
static String firstLine(Path path) throws IOException {
try (BufferedReader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
return reader.readLine();
}
}
}
try-with-resources 在正常结束或抛异常时调用 close(),适用于实现 AutoCloseable 的对象。数据库连接、流、压缩包等都应有清晰的所有者。finally 适合仍需手动清理的场景,但不要在其中 return 遮蔽原异常。
3. 业务错误与事务
“订单不存在”“余额不足”是可预期的业务结果;数据库中断、程序空指针是系统故障。两者应有不同的日志、HTTP 状态和监控方式。事务内抛异常后是否自动回滚,取决于所用事务框架和异常类型的规则;不要凭感觉假设所有异常都回滚。
自测
Files.newBufferedReader获取成功后,哪种结构能自动关闭?try-with-resources。- 为什么不应把异常堆栈直接返回给用户?会泄露内部细节,且不是稳定的 API 契约。
4. 选择捕获层与保留原因
底层 I/O 层知道“读文件失败”,业务层知道“配置无法加载”,HTTP 层知道要返回什么状态。每层都打印堆栈会重复记录;更好的做法是在能补充语义的地方包装异常,在请求边界统一记录与映射。不可恢复的程序错误应暴露给监控,不要用 catch (Exception) 把它伪装成正常空结果。
class ConfigLoadException extends RuntimeException {
ConfigLoadException(String message, Throwable cause) {
super(message, cause);
}
}
抛出时传入原异常作为 cause,排查时才能看到根因。业务错误若被 API 契约定义为 404 或 409,应与数据库不可用导致的 500 区分。
5. try-with-resources 的细节
多个资源可在同一个 try (...) 中声明,关闭顺序与声明顺序相反。若业务逻辑和 close() 都抛异常,关闭异常会成为被抑制异常(suppressed exception);排查时不要只看最外层消息。资源所有权必须清晰:由方法创建的流通常由该方法关闭;从外部借用的流不应擅自关闭。
在 Spring 事务中,默认回滚规则与异常类型有关;受检异常是否回滚要看注解配置。写入服务要通过实际测试验证“第二步失败后第一步是否撤销”。