Java 清理失败盖住了原始错误:用 suppressed 找回故障链
导入失败后,日志只有关闭文件的异常,最早的解析错误丢了。手写 finally 中抛出的异常可能覆盖正在传播的异常。try-with-resources 会保留主体异常,并通过 getSuppressed 提供关闭时的异常。
1. 同时注入解析和关闭故障
保存为 Lab.java,使用 JDK 17 运行 java -ea Lab.java。
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
class Lab {
record Resource(String name, List<String> closed) implements AutoCloseable {
public void close() throws IOException {
closed.add(name);
throw new IOException("close " + name);
}
}
static void manual(List<String> closed) throws IOException {
var input = new Resource("input", closed);
try {
throw new IOException("parse failed");
} finally {
input.close();
}
}
public static void main(String[] args) {
var closed = new ArrayList<String>();
try {
manual(closed);
throw new AssertionError("missing exception");
} catch (IOException error) {
assert error.getMessage().equals("close input");
System.out.println("finally primary: " + error.getMessage());
}
closed.clear();
try (var input = new Resource("input", closed);
var output = new Resource("output", closed)) {
throw new IOException("parse failed");
} catch (IOException error) {
assert error.getMessage().equals("parse failed");
assert closed.equals(List.of("output", "input"));
var suppressed = error.getSuppressed();
assert suppressed.length == 2;
assert suppressed[0].getMessage().equals("close output");
assert suppressed[1].getMessage().equals("close input");
System.out.println("resource primary: " + error.getMessage());
System.out.println("close order: " + closed);
for (var failure : suppressed) {
System.out.println("suppressed: " + failure.getMessage());
}
}
}
}
本机实测输出如下。
finally primary: close input
resource primary: parse failed
close order: [output, input]
suppressed: close output
suppressed: close input
2. 日志和事务各管什么
两个资源按声明的逆序关闭。关闭也可能是业务操作的一部分,例如输出流在 close 时刷新最后一批字节。主体正常结束时,首个关闭异常会成为主异常,因此成功计数应放在 try 块退出之后。
| 现场现象 | 检查位置 |
|---|---|
| 最早异常消失 | finally 中的关闭或清理是否抛出新异常 |
| 关闭错误没有入日志 | 日志调用是否传入完整 Throwable |
| 写入看似成功但文件损坏 | 输出流关闭成功前是否已发布文件 |
捕获异常后若要增加上下文,保留原异常作为 cause,这样它的 suppressed 信息依然可以查看。外部事务是否回滚由事务边界决定,关闭动作本身按资源实现处理。
小结
先读主异常定位首个失败,再沿 suppressed 检查清理阶段。把资源块完整退出作为记录成功的条件。
数据来源与检索时间
检索时间 2026-11-13。
| 来源 | URL | 对应内容 |
|---|---|---|
| Java 17 语言规范 §14.20.3 | https://docs.oracle.com/javase/specs/jls/se17/html/jls-14.html#jls-14.20.3 | 关闭顺序、主异常与 suppressed |
核验说明
- OpenJDK 17.0.19 启用断言运行。关闭故障由测试资源注入。