Java iTextPDF循环批量生成PDF异常问题求助
iTextPDF批量生成PDF异常排查与修复
问题现象
- 单独生成单个PDF完全正常,无任何问题
- 循环批量执行生成逻辑时,生成的PDF数量不稳定(有时1个,有时2个或5个),部分文件存在错误
- 尝试关闭
DocWriter时会抛出异常
原实现代码
public class ImprimirRecogida { Font font = FontFactory.getFont(FontFactory.HELVETICA, 10); private PdfWriter docWriter = null; private PdfContentByte cb; FileOutputStream fOS; Document document = new Document(PageSize.A4);//(842, public String crearAutodeclaracion(String[] cli, String rutaFirma) { try{ String codigo = cli[0]; String nombre = cli[1]; String direccion = cli[2]; String codPostal = cli[3]; String poblacion = cli[4]; Calendar cal = Calendar.getInstance(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy"); File ruta = new File("C:\\Informes\\autodeclaraciones\\"); try{ String NOMBRE = new String(); NOMBRE = "AUTODECLARACION_"+codigo+"_"+sdf.format(cal.getTime())+".pdf"; String str = "C:\\Informes\\autodeclaraciones\\"+NOMBRE; fOS = new FileOutputStream(str); docWriter = PdfWriter.getInstance(document, fOS);//new FileOutputStream(FILE));//+System.currentTimeMillis()+".pdf")); document.open(); cb = docWriter.getDirectContent(); document.open(); PdfReader reader = new PdfReader(ruta.getPath()+"\\"+"ISCC.pdf"); PdfImportedPage page; page = docWriter.getImportedPage(reader, 1); Image instance = Image.getInstance(page); document.newPage(); document.add(instance); try{ Image firmaCliente = Image.getInstance(rutaFirma); firmaCliente.setAlignment(Image.RIGHT | Image.TEXTWRAP); firmaCliente.scaleAbsolute(80, 45); firmaCliente.setAbsolutePosition(425, 70); document.add(firmaCliente); } catch (Exception e) { e.printStackTrace(); } try{ addCB(225, 670 , nombre); addCB(225, 610 , direccion); addCB(225, 590 , codPostal+", "+poblacion); addCB(225, 550 , "ESPAÑA"); addCB(225, 530 , " S.A."); addCB(560, 315 , "X"); addCB(102, 90 , poblacion+","+ obtenerFechaParaAutodeclaracion()); } catch (Exception e) { e.printStackTrace(); } document.close(); fOS.close(); return ruta.getPath()+"/"+NOMBRE; } catch (Exception e) { e.printStackTrace(); } } catch (Exception ee){ ee.printStackTrace(); } return ""; } private void addCB(float x, float y, String texto) { try { if(null == texto) { texto = ""; } cb.beginText(); BaseFont bFRegular; bFRegular = BaseFont.createFont(BaseFont.HELVETICA, BaseFont.CP1252, BaseFont.NOT_EMBEDDED); cb.setFontAndSize(bFRegular, 12); cb.setTextMatrix(x, y); //(xPos, yPos) cb.showText(texto); cb.endText(); } catch (Exception e) { e.printStackTrace(); } } public String obtenerFechaParaAutodeclaracion() { //15-19,22-26(incluidos) try { Random rand = new Random(); // nextInt is normally exclusive of the top value, // so add 1 to make it inclusive int randomNum = rand.nextInt((29 - 15) + 1) + 15; StringBuilder sBFechaAutodec = new StringBuilder(); sBFechaAutodec.append(randomNum); sBFechaAutodec.append("/01/"); sBFechaAutodec.append(UtilsVictor.getCurrentYear4Digits()); boolean encontrado = false; while (!encontrado) { Calendar cal = Calendar.getInstance(); SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy"); cal.setTime(sdf.parse(sBFechaAutodec.toString())); if ((cal.get(Calendar.DAY_OF_WEEK) != Calendar.SATURDAY && cal.get(Calendar.DAY_OF_WEEK) != Calendar.SUNDAY)){ encontrado = true; return sBFechaAutodec.toString(); } else { encontrado = false; } } } catch (ParseException e) { e.printStackTrace(); } return ""; } }
原调用逻辑
for(int i=0;i<100;i++) { ImprimirRecogidaRiojana mImprimirRecogidaRiojana = new ImprimirRecogidaRiojana(); mImprimirRecogidaRiojana.crearAutodeclaracion(new String[] {System.currentTimeMillis()+"","nnnombre","dddirecion","cccodPost","ppppoblacion",""},"C:\\Informes\\autodeclaraciones\\firma.png"); }
核心问题分析
成员变量复用导致资源冲突
Document、PdfWriter、FileOutputStream等被定义为类成员变量,批量生成时,异常场景下资源未正确释放,会导致后续实例的资源操作混乱。重复调用
document.open()
代码中连续两次调用document.open(),第一次打开后第二次调用会抛出异常,虽被捕获但会导致PDF文件生成不完整。资源未在finally块中释放
document、fOS、PdfReader等资源仅在正常流程中关闭,异常时会导致资源泄漏,长时间批量运行会耗尽内存或文件句柄。BaseFont重复创建addCB方法中每次都创建新的BaseFont实例,大量重复创建会浪费内存,影响批量生成性能。无限循环风险
obtenerFechaParaAutodeclaracion方法中,如果随机生成的日期一直是周末,会进入无限循环,导致线程阻塞,批量生成中断。
修复后的代码
public class ImprimirRecogida { // 复用BaseFont,避免重复创建 private static final BaseFont HELVETICA_BASE_FONT; static { try { HELVETICA_BASE_FONT = BaseFont.createFont(BaseFont.HELVETICA, BaseFont.CP1252, BaseFont.NOT_EMBEDDED); } catch (Exception e) { throw new RuntimeException("Failed to initialize base font", e); } } public String crearAutodeclaracion(String[] cli, String rutaFirma) { Document document = null; PdfWriter docWriter = null; FileOutputStream fOS = null; PdfReader reader = null; try { String codigo = cli[0]; String nombre = cli[1]; String direccion = cli[2]; String codPostal = cli[3]; String poblacion = cli[4]; Calendar cal = Calendar.getInstance(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy"); File ruta = new File("C:\\Informes\\autodeclaraciones\\"); String nombreArchivo = "AUTODECLARACION_" + codigo + "_" + sdf.format(cal.getTime()) + ".pdf"; String rutaArchivo = "C:\\Informes\\autodeclaraciones\\" + nombreArchivo; fOS = new FileOutputStream(rutaArchivo); document = new Document(PageSize.A4); docWriter = PdfWriter.getInstance(document, fOS); document.open(); PdfContentByte cb = docWriter.getDirectContent(); // 读取模板PDF reader = new PdfReader(ruta.getPath() + "\\" + "ISCC.pdf"); PdfImportedPage page = docWriter.getImportedPage(reader, 1); Image instance = Image.getInstance(page); document.newPage(); document.add(instance); // 添加签名图片 try { Image firmaCliente = Image.getInstance(rutaFirma); firmaCliente.setAlignment(Image.RIGHT | Image.TEXTWRAP); firmaCliente.scaleAbsolute(80, 45); firmaCliente.setAbsolutePosition(425, 70); document.add(firmaCliente); } catch (Exception e) { e.printStackTrace(); } // 添加文本内容 addCB(cb, 225, 670, nombre); addCB(cb, 225, 610, direccion); addCB(cb, 225, 590, codPostal + ", " + poblacion); addCB(cb, 225, 550, "ESPAÑA"); addCB(cb, 225, 530, " S.A."); addCB(cb, 560, 315, "X"); addCB(cb, 102, 90, poblacion + "," + obtenerFechaParaAutodeclaracion()); return ruta.getPath() + "/" + nombreArchivo; } catch (Exception e) { e.printStackTrace(); return ""; } finally { // 确保所有资源都被释放 if (reader != null) { reader.close(); } if (document != null && document.isOpen()) { document.close(); } if (docWriter != null) { docWriter.close(); } if (fOS != null) { try { fOS.close(); } catch (IOException e) { e.printStackTrace(); } } } } private void addCB(PdfContentByte cb, float x, float y, String texto) { try { if (texto == null) { texto = ""; } cb.beginText(); cb.setFontAndSize(HELVETICA_BASE_FONT, 12); cb.setTextMatrix(x, y); cb.showText(texto); cb.endText(); } catch (Exception e) { e.printStackTrace(); } } public String obtenerFechaParaAutodeclaracion() { Random rand = new Random(); int maxAttempts = 10; // 限制最大尝试次数,避免无限循环 int attempts = 0; while (attempts < maxAttempts) { attempts++; int randomNum = rand.nextInt((29 - 15) + 1) + 15; StringBuilder sBFechaAutodec = new StringBuilder(); sBFechaAutodec.append(randomNum); sBFechaAutodec.append("/01/"); sBFechaAutodec.append(UtilsVictor.getCurrentYear4Digits()); try { Calendar cal = Calendar.getInstance(); SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy"); cal.setTime(sdf.parse(sBFechaAutodec.toString())); int dayOfWeek = cal.get(Calendar.DAY_OF_WEEK); if (dayOfWeek != Calendar.SATURDAY && dayOfWeek != Calendar.SUNDAY) { return sBFechaAutodec.toString(); } } catch (ParseException e) { e.printStackTrace(); } } // 如果多次尝试失败,返回默认日期 return "15/01/" + UtilsVictor.getCurrentYear4Digits(); } }
关键修复点说明
- 资源局部化:将
Document、PdfWriter等资源定义为方法局部变量,避免实例间资源冲突,确保每次生成都是独立的资源上下文。 - finally块释放资源:所有IO资源和iText对象都在finally块中关闭,无论正常还是异常流程都能保证资源释放,避免泄漏。
- 移除重复的
document.open():只调用一次document.open(),避免异常。 - 复用
BaseFont:静态初始化BaseFont,全局复用,减少内存开销。 - 限制日期生成尝试次数:避免无限循环导致批量生成中断。
- 明确关闭
PdfReader:原代码未关闭模板PDF的PdfReader,会导致文件句柄泄漏。
内容的提问来源于stack exchange,提问作者vLopez
相关产品推荐
相关产品推荐

