移除PDPage后关闭原PDDocument为何仍引发COSStream关闭异常?
PDFBox页面合并后关闭临时文档导致COSStream异常问题
我尝试渲染新页面并附加到现有PDF,代码如下:
PDDocument withNewPage = renderNewPage(); withNewPage.getPages().forEach(page -> { markNeedToBeUpdated( page, page.getResources(), page.getResources().getCOSObject().getDictionary(COSName.XOBJECT) ); withNewPage.removePage(page); original.addPage(page); });
这段代码原本可运行,但因未关闭withNewPage收到终结器警告。添加withNewPage.close()后,导出original为字节时出现以下异常:
java.io.IOException: COSStream has been closed and cannot be read. Perhaps its enclosing PDDocument has been closed? at org.apache.pdfbox.cos.COSStream.checkClosed(COSStream.java:83) at org.apache.pdfbox.cos.COSStream.createRawInputStream(COSStream.java:133) at org.apache.pdfbox.pdfwriter.COSWriter.visitFromStream(COSWriter.java:1288) at org.apache.pdfbox.cos.COSStream.accept(COSStream.java:416) at org.apache.pdfbox.pdfwriter.COSWriter.doWriteObject(COSWriter.java:570) at org.apache.pdfbox.pdfwriter.COSWriter.doWriteObjects(COSWriter.java:496) at org.apache.pdfbox.pdfwriter.COSWriter.doWriteBody(COSWriter.java:480) at org.apache.pdfbox.pdfwriter.COSWriter.visitFromDocument(COSWriter.java:1182) at org.apache.pdfbox.cos.COSDocument.accept(COSDocument.java:452) at org.apache.pdfbox.pdfwriter.COSWriter.write(COSWriter.java:1455) at org.apache.pdfbox.pdmodel.PDDocument.saveIncremental(PDDocument.java:1421)
我调试了close调用,发现新页面本身不在withNewPage.close()关闭的列表中,但部分COSStream对象被关闭。
疑问
- 为何调用
removePage无法阻止页面(或其流)随原文档关闭? - 如何干净完成该合并操作?
我看过相关问题,但因已移除页面所以预期代码可行,且importPage文档说明不适用于生成文档,尝试后仍出现相同异常。
解答
问题根源
PDFBox中,页面的资源(包括COSStream)属于创建它的PDDocument对应的COSDocument管理。即使把页面从临时文档移除并添加到目标文档,底层COS对象仍然和原文档的资源池绑定。调用withNewPage.close()时,会关闭所有关联的COSStream——不管页面是否被移走,因为这些流的所有权仍在临时文档的COSDocument手中。
解决方案
不要直接移动页面,而是通过导入或复制让目标文档接管资源所有权,以下是两种可靠方案:
使用
importPage(推荐)
官方文档所谓的“不适用于生成文档”仅针对部分复杂动态场景,大多数生成文档的场景下importPage可以正常工作,它会自动处理资源的关联与转移:try (PDDocument withNewPage = renderNewPage()) { for (PDPage page : withNewPage.getPages()) { PDPage importedPage = original.importPage(page); // 注意操作的是导入后的页面 markNeedToBeUpdated( importedPage, importedPage.getResources(), importedPage.getResources().getCOSObject().getDictionary(COSName.XOBJECT) ); } } // try-with-resources自动关闭临时文档,不会影响原文档资源手动复制页面资源(备选)
如果importPage确实不适用,可手动复制页面内容与资源到原文档的新页面:try (PDDocument withNewPage = renderNewPage()) { for (PDPage page : withNewPage.getPages()) { // 创建与原页面尺寸一致的新页面 PDPage newPage = new PDPage(page.getMediaBox()); original.addPage(newPage); // 复制页面内容 try (PDPageContentStream contentStream = new PDPageContentStream(original, newPage, PDPageContentStream.AppendMode.OVERWRITE, true)) { contentStream.drawForm(page); } // 复制并关联资源 PDResources srcResources = page.getResources(); PDResources destResources = newPage.getResources(); destResources.add(srcResources); // 执行更新标记逻辑 markNeedToBeUpdated( newPage, destResources, destResources.getCOSObject().getDictionary(COSName.XOBJECT) ); } }
关键注意点
- 始终用try-with-resources语法管理
PDDocument,自动完成资源释放,避免泄漏。 - 页面底层COS对象的所有权属于创建它的文档,直接移动页面不会转移所有权,必须通过导入或复制让目标文档接管。
内容的提问来源于stack exchange,提问作者chrylis -cautiouslyoptimistic-
相关产品推荐
相关产品推荐

