You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java XML Transformer未关闭文件致Linux出现“打开文件过多”错误

解决Saxon XSL导入文件句柄泄漏问题

问题根源

当前URIResolver手动创建InputStream并返回StreamSource,但Saxon的JAXP实现不会自动关闭这些流;同时每次调用newTransformer都会重新解析样式表及导入文件,重复打开流且未关闭,最终触发"Too many open files"错误。直接在URIResolver中用try-with-resource会导致流被提前关闭,Saxon后续读取时抛出I/O异常。

解决方案

方案1:让Saxon直接处理文件(最简单)

修改URIResolver,直接返回基于文件的StreamSource,而非手动打开的InputStream。Saxon会自行负责文件流的打开与关闭,避免泄漏:

transformerFactory.setURIResolver((arg0, arg1) -> {
    try {
        Path xslPath = targetFolderPath.resolve(arg0);
        // 直接传入File,让Saxon管理流生命周期
        return new StreamSource(xslPath.toFile());
    } catch (Exception e) {
        LOGGER.error(e.getLocalizedMessage(), e);
        return null;
    }
});

方案2:缓存Transformer实例(优化性能+减少句柄占用)

如果样式表内容不会动态变化,不要每次转换都创建新的Transformer。缓存Transformer实例复用,避免重复解析样式表及导入文件,从根源减少文件句柄的打开次数:

// 类级别缓存,确保只初始化一次
private static Transformer cachedTransformer;

// 初始化方法(可放在静态代码块或首次调用时初始化)
private static Transformer getCachedTransformer() throws IOException, TransformerException {
    if (cachedTransformer == null) {
        TransformerFactoryImpl transformerFactory = new TransformerFactoryImpl();
        Path targetFolderPath = <你的路径>;
        transformerFactory.setURIResolver((arg0, arg1) -> {
            try {
                return new StreamSource(targetFolderPath.resolve(arg0).toFile());
            } catch (Exception e) {
                LOGGER.error(e.getLocalizedMessage(), e);
                return null;
            }
        });
        try (InputStream xslStream = Files.newInputStream(Paths.get(pathToTheXslFile))) {
            cachedTransformer = transformerFactory.newTransformer(new StreamSource(xslStream));
        }
    }
    return cachedTransformer;
}

// 转换逻辑
try {
    Transformer transformer = getCachedTransformer();
    transformer.transform(XmlData, new StreamResult(destFile));
} catch (IOException | TransformerException e) {
    // 日志处理
}

方案3:自定义可关闭的StreamSource(适配特殊场景)

如果必须使用InputStream(比如从Jar包或网络读取样式表),可以自定义一个包装类,让Saxon在处理完成后关闭流:

class CloseableStreamSource extends StreamSource {
    private final InputStream stream;

    public CloseableStreamSource(InputStream stream) {
        super(stream);
        this.stream = stream;
    }

    @Override
    public void setInputStream(InputStream inputStream) {
        throw new UnsupportedOperationException();
    }

    // 手动关闭方法,在样式表解析完成后调用
    public void close() throws IOException {
        if (stream != null) {
            stream.close();
        }
    }
}

使用时需确保在Transformer创建完成后关闭所有导入文件的流:

try (InputStream mainXslStream = Files.newInputStream(Paths.get(pathToTheXslFile))) {
    List<CloseableStreamSource> sourcesToClose = new ArrayList<>();
    TransformerFactoryImpl transformerFactory = new TransformerFactoryImpl();
    transformerFactory.setURIResolver((arg0, arg1) -> {
        try {
            InputStream xslStream = Files.newInputStream(targetFolderPath.resolve(arg0));
            CloseableStreamSource source = new CloseableStreamSource(xslStream);
            sourcesToClose.add(source);
            return source;
        } catch (IOException e) {
            LOGGER.error(e.getLocalizedMessage(), e);
            return null;
        }
    });

    Transformer transformer = transformerFactory.newTransformer(new StreamSource(mainXslStream));
    // 样式表解析完成,关闭所有导入文件的流
    for (CloseableStreamSource source : sourcesToClose) {
        source.close();
    }

    transformer.transform(XmlData, new StreamResult(destFile));
} catch (IOException | TransformerException e) {
    // 日志处理
}

注意事项

  • 优先选择方案1或方案2,代码改动小且效果显著;
  • 若使用方案3,必须确保流在样式表解析完成后再关闭,避免提前关闭导致读取异常;
  • 若样式表会动态更新,缓存Transformer时需添加失效机制,避免使用旧样式表。

内容的提问来源于stack exchange,提问作者TonioLeParigo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 04:47:26