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

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");
}

核心问题分析

  1. 成员变量复用导致资源冲突
    Document、PdfWriter、FileOutputStream等被定义为类成员变量,批量生成时,异常场景下资源未正确释放,会导致后续实例的资源操作混乱。

  2. 重复调用document.open()
    代码中连续两次调用document.open(),第一次打开后第二次调用会抛出异常,虽被捕获但会导致PDF文件生成不完整。

  3. 资源未在finally块中释放
    document、fOS、PdfReader等资源仅在正常流程中关闭,异常时会导致资源泄漏,长时间批量运行会耗尽内存或文件句柄。

  4. BaseFont重复创建
    addCB方法中每次都创建新的BaseFont实例,大量重复创建会浪费内存,影响批量生成性能。

  5. 无限循环风险
    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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 10:20:55