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
相关产品推荐
相关产品推荐

