KernelRanch
返回文章列表

Java 清理失败盖住了原始错误:用 suppressed 找回故障链

2026-11-13 #java#exceptions#resources

导入失败后,日志只有关闭文件的异常,最早的解析错误丢了。手写 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.3https://docs.oracle.com/javase/specs/jls/se17/html/jls-14.html#jls-14.20.3关闭顺序、主异常与 suppressed

核验说明

  • OpenJDK 17.0.19 启用断言运行。关闭故障由测试资源注入。