java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > SpringBoot集成iText快速生成PDF

SpringBoot集成iText快速生成PDF教程

作者:shy好好学习

本文介绍了如何在SpringBoot项目中集成iText9.4.0生成PDF文档,包括新特性的介绍、环境准备、Service层实现、Controller编写、优劣分析、不同版本对比、版本选择建议以及最佳实践总结

SpringBoot集成iText 9.4.0生成PDF

2025年iText已发展到9.x版本,本文将带你体验最新特性和最佳实践

在现代企业应用开发中,动态生成PDF文档是一个常见需求。无论是生成报表、发票、合同还是其他业务单据,PDF格式因其跨平台、保持布局不变的特性而备受青睐。

本文将详细介绍如何在SpringBoot项目中集成iText 9.4.0——当前最新的版本,来实现高效、专业的PDF生成。

一、iText 9新特性与架构变革

iText 9.x相比旧版本进行了彻底的重构:

二、环境准备与依赖配置

Maven依赖配置

在SpringBoot项目的pom.xml中添加以下依赖:

<properties>
    <itext.version>9.4.0</itext.version>
</properties>

<dependencies>
    <!-- iText 9 核心模块 -->
    <dependency>
        <groupId>com.itextpdf</groupId>
        <artifactId>kernel</artifactId>
        <version>${itext.version}</version>
    </dependency>
    <dependency>
        <groupId>com.itextpdf</groupId>
        <artifactId>io</artifactId>
        <version>${itext.version}</version>
    </dependency>
    <dependency>
        <groupId>com.itextpdf</groupId>
        <artifactId>layout</artifactId>
        <version>${itext.version}</version>
    </dependency>
    <dependency>
        <groupId>com.itextpdf</groupId>
        <artifactId>forms</artifactId>
        <version>${itext.version}</version>
    </dependency>
    <!-- 条形码支持 -->
    <dependency>
        <groupId>com.itextpdf</groupId>
        <artifactId>barcodes</artifactId>
        <version>${itext.version}</version>
    </dependency>
    <!-- 中文支持 -->
    <dependency>
        <groupId>com.itextpdf</groupId>
        <artifactId>font-asian</artifactId>
        <version>${itext.version}</version>
    </dependency>
</dependencies>

三、完整的Service层实现

1. 基础PDF服务

@Service
public class PdfService {
    
    /**
     * 创建简单PDF文档
     */
    public void createSimplePdf(OutputStream outputStream) throws IOException {
        PdfWriter writer = new PdfWriter(outputStream);
        PdfDocument pdfDoc = new PdfDocument(writer);
        Document document = new Document(pdfDoc);
        
        // 添加内容
        document.add(new Paragraph("Hello, iText 9.4.0!")
            .setFontSize(20)
            .setBold());
        document.add(new Paragraph("这是一个使用SpringBoot和iText 9生成的PDF文档"));
        document.add(new Paragraph("生成时间: " + new Date()));
        
        // 关闭文档
        document.close();
    }
    
    /**
     * 创建带表格的PDF
     */
    public void createTablePdf(OutputStream outputStream) throws IOException {
        PdfWriter writer = new PdfWriter(outputStream);
        PdfDocument pdfDoc = new PdfDocument(writer);
        Document document = new Document(pdfDoc);
        
        // 创建3列表格
        Table table = new Table(3);
        
        // 添加表头
        table.addHeaderCell(new Cell().add(new Paragraph("姓名").setBold()));
        table.addHeaderCell(new Cell().add(new Paragraph("部门").setBold()));
        table.addHeaderCell(new Cell().add(new Paragraph("工资").setBold()));
        
        // 添加数据行
        table.addCell(new Cell().add(new Paragraph("张三")));
        table.addCell(new Cell().add(new Paragraph("技术部")));
        table.addCell(new Cell().add(new Paragraph("8000")));
        
        table.addCell(new Cell().add(new Paragraph("李四")));
        table.addCell(new Cell().add(new Paragraph("市场部")));
        table.addCell(new Cell().add(new Paragraph("7500")));
        
        table.addCell(new Cell().add(new Paragraph("王五")));
        table.addCell(new Cell().add(new Paragraph("财务部")));
        table.addCell(new Cell().add(new Paragraph("8200")));
        
        document.add(new Paragraph("员工信息表").setFontSize(16).setBold());
        document.add(table);
        document.close();
    }
    
    /**
     * 生成带条形码的PDF
     */
    public void createPdfWithBarcode(OutputStream outputStream, String barcodeData) throws IOException {
        PdfWriter writer = new PdfWriter(outputStream);
        PdfDocument pdfDoc = new PdfDocument(writer);
        Document document = new Document(pdfDoc);
        
        // 添加标题
        document.add(new Paragraph("条形码示例文档")
            .setFontSize(18)
            .setBold()
            .setTextAlignment(TextAlignment.CENTER));
        
        // 添加条形码
        document.add(new Paragraph("条形码数据: " + barcodeData));
        
        // 创建Code 128条形码
        Barcode128 barcode = new Barcode128(pdfDoc);
        barcode.setCode(barcodeData);
        barcode.setFont(null); // 不显示文本
        
        // 将条形码转换为图像
        Image barcodeImage = new Image(barcode.createFormXObject(pdfDoc))
            .setWidth(200)
            .setAutoScaleHeight(true);
        
        document.add(barcodeImage);
        document.close();
    }
    
    /**
     * 生成带图片和条形码的PDF
     */
    public void createPdfWithImageAndBarcode(OutputStream outputStream, String imageUrl, String barcodeData) throws IOException {
        PdfWriter writer = new PdfWriter(outputStream);
        PdfDocument pdfDoc = new PdfDocument(writer);
        Document document = new Document(pdfDoc);
        
        // 添加标题
        document.add(new Paragraph("产品信息卡")
            .setFontSize(18)
            .setBold()
            .setTextAlignment(TextAlignment.CENTER));
        
        // 添加图片(如果有)
        if (imageUrl != null && !imageUrl.trim().isEmpty()) {
            try {
                Image image = new Image(ImageDataFactory.create(imageUrl))
                    .setWidth(200)
                    .setAutoScaleHeight(true)
                    .setHorizontalAlignment(HorizontalAlignment.CENTER);
                document.add(image);
            } catch (Exception e) {
                document.add(new Paragraph("图片加载失败: " + e.getMessage()));
            }
        }
        
        // 添加条形码
        if (barcodeData != null && !barcodeData.trim().isEmpty()) {
            Barcode128 barcode = new Barcode128(pdfDoc);
            barcode.setCode(barcodeData);
            
            Image barcodeImage = new Image(barcode.createFormXObject(pdfDoc))
                .setWidth(250)
                .setAutoScaleHeight(true)
                .setHorizontalAlignment(HorizontalAlignment.CENTER);
            
            document.add(new Paragraph("产品条形码:").setBold());
            document.add(barcodeImage);
        }
        
        document.close();
    }
    
    /**
     * 创建报告PDF
     */
    public void createReportPdf(OutputStream outputStream) throws IOException {
        PdfWriter writer = new PdfWriter(outputStream);
        PdfDocument pdfDoc = new PdfDocument(writer);
        Document document = new Document(pdfDoc);
        
        // 报告标题
        document.add(new Paragraph("月度销售报告")
            .setFontSize(24)
            .setBold()
            .setTextAlignment(TextAlignment.CENTER));
        
        // 报告信息
        document.add(new Paragraph("报告周期: " + new Date()));
        document.add(new Paragraph("生成部门: 销售部"));
        
        // 销售数据表格
        Table salesTable = new Table(4);
        salesTable.addHeaderCell(new Cell().add(new Paragraph("产品").setBold()));
        salesTable.addHeaderCell(new Cell().add(new Paragraph("季度").setBold()));
        salesTable.addHeaderCell(new Cell().add(new Paragraph("销售额").setBold()));
        salesTable.addHeaderCell(new Cell().add(new Paragraph("增长率").setBold()));
        
        // 示例数据
        String[][] salesData = {
            {"产品A", "Q1", "¥120,000", "15%"},
            {"产品A", "Q2", "¥138,000", "15%"},
            {"产品B", "Q1", "¥85,000", "8%"},
            {"产品B", "Q2", "¥92,000", "8%"}
        };
        
        for (String[] row : salesData) {
            for (String cell : row) {
                salesTable.addCell(new Cell().add(new Paragraph(cell)));
            }
        }
        
        document.add(salesTable);
        document.close();
    }
    
    /**
     * 创建发票PDF
     */
    public void createInvoicePdf(OutputStream outputStream) throws IOException {
        PdfWriter writer = new PdfWriter(outputStream);
        PdfDocument pdfDoc = new PdfDocument(writer);
        Document document = new Document(pdfDoc);
        
        // 发票标题
        document.add(new Paragraph("商业发票")
            .setFontSize(20)
            .setBold()
            .setTextAlignment(TextAlignment.CENTER));
        
        // 发票信息表格
        Table invoiceTable = new Table(2);
        invoiceTable.addCell(new Cell().add(new Paragraph("发票号码:").setBold()));
        invoiceTable.addCell(new Cell().add(new Paragraph("INV-2024-001")));
        invoiceTable.addCell(new Cell().add(new Paragraph("开票日期:").setBold()));
        invoiceTable.addCell(new Cell().add(new Paragraph(new Date().toString())));
        invoiceTable.addCell(new Cell().add(new Paragraph("客户名称:").setBold()));
        invoiceTable.addCell(new Cell().add(new Paragraph("某某科技有限公司")));
        
        document.add(invoiceTable);
        
        // 商品明细表格
        Table itemsTable = new Table(4);
        itemsTable.addHeaderCell(new Cell().add(new Paragraph("商品名称").setBold()));
        itemsTable.addHeaderCell(new Cell().add(new Paragraph("数量").setBold()));
        itemsTable.addHeaderCell(new Cell().add(new Paragraph("单价").setBold()));
        itemsTable.addHeaderCell(new Cell().add(new Paragraph("金额").setBold()));
        
        itemsTable.addCell(new Cell().add(new Paragraph("笔记本电脑")));
        itemsTable.addCell(new Cell().add(new Paragraph("2")));
        itemsTable.addCell(new Cell().add(new Paragraph("¥5,999")));
        itemsTable.addCell(new Cell().add(new Paragraph("¥11,998")));
        
        itemsTable.addCell(new Cell().add(new Paragraph("无线鼠标")));
        itemsTable.addCell(new Cell().add(new Paragraph("5")));
        itemsTable.addCell(new Cell().add(new Paragraph("¥89")));
        itemsTable.addCell(new Cell().add(new Paragraph("¥445")));
        
        document.add(new Paragraph("商品明细:").setBold());
        document.add(itemsTable);
        
        // 总计
        document.add(new Paragraph("总计: ¥12,443").setBold().setFontSize(16));
        
        document.close();
    }
}

2. 中文PDF服务

@Service
public class ChinesePdfService {
    
    /**
     * 创建支持中文的PDF文档
     */
    public void createChinesePdf(OutputStream outputStream) throws IOException {
        PdfWriter writer = new PdfWriter(outputStream);
        PdfDocument pdfDoc = new PdfDocument(writer);
        Document document = new Document(pdfDoc);
        
        // 使用中文字体
        PdfFont chineseFont = PdfFontFactory.createFont("STSong-Light", "UniGB-UCS2-H", true);
        
        // 添加中文内容
        document.add(new Paragraph("中文PDF文档示例")
            .setFont(chineseFont)
            .setFontSize(20)
            .setBold()
            .setTextAlignment(TextAlignment.CENTER));
        
        document.add(new Paragraph("企业名称:某某科技有限公司")
            .setFont(chineseFont));
        
        document.add(new Paragraph("公司地址:北京市海淀区中关村大街1号")
            .setFont(chineseFont));
        
        document.add(new Paragraph("联系电话:010-12345678")
            .setFont(chineseFont));
        
        // 中文表格
        Table table = new Table(3);
        table.addHeaderCell(new Cell().add(new Paragraph("姓名").setFont(chineseFont).setBold()));
        table.addHeaderCell(new Cell().add(new Paragraph("职位").setFont(chineseFont).setBold()));
        table.addHeaderCell(new Cell().add(new Paragraph("部门").setFont(chineseFont).setBold()));
        
        table.addCell(new Cell().add(new Paragraph("张三").setFont(chineseFont)));
        table.addCell(new Cell().add(new Paragraph("软件工程师").setFont(chineseFont)));
        table.addCell(new Cell().add(new Paragraph("技术部").setFont(chineseFont)));
        
        table.addCell(new Cell().add(new Paragraph("李四").setFont(chineseFont)));
        table.addCell(new Cell().add(new Paragraph("产品经理").setFont(chineseFont)));
        table.addCell(new Cell().add(new Paragraph("产品部").setFont(chineseFont)));
        
        document.add(table);
        document.close();
    }
}

3. 模板PDF服务

@Service
public class TemplatePdfService {
    
    /**
     * 基于模板填充PDF表单
     */
    public void fillPdfTemplate(OutputStream outputStream, Map<String, String> formData) throws IOException {
        // 创建临时模板(实际项目中应该从文件系统或数据库读取模板)
        byte[] templateBytes = createTemplatePdf();
        
        // 读取模板
        PdfReader reader = new PdfReader(new ByteArrayInputStream(templateBytes));
        PdfWriter writer = new PdfWriter(outputStream);
        PdfDocument pdfDoc = new PdfDocument(reader, writer);
        
        // 获取表单
        PdfAcroForm form = PdfAcroForm.getAcroForm(pdfDoc, true);
        
        // 填充数据
        for (Map.Entry<String, String> entry : formData.entrySet()) {
            String fieldName = entry.getKey();
            String fieldValue = entry.getValue();
            PdfFormField field = form.getField(fieldName);
            if (field != null) {
                field.setValue(fieldValue);
            }
        }
        
        // 扁平化表单(使字段不可编辑)
        form.flattenFields();
        
        pdfDoc.close();
    }
    
    /**
     * 创建示例模板PDF(实际项目中应使用现有的PDF模板)
     */
    private byte[] createTemplatePdf() throws IOException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        PdfWriter writer = new PdfWriter(baos);
        PdfDocument pdfDoc = new PdfDocument(writer);
        
        // 创建表单
        PdfAcroForm form = PdfAcroForm.getAcroForm(pdfDoc, true);
        
        // 添加表单字段
        Rectangle rect = new Rectangle(100, 700, 200, 20);
        PdfTextFormField nameField = PdfTextFormField.createText(pdfDoc, rect, "name", "");
        form.addField(nameField);
        
        rect = new Rectangle(100, 650, 200, 20);
        PdfTextFormField emailField = PdfTextFormField.createText(pdfDoc, rect, "email", "");
        form.addField(emailField);
        
        rect = new Rectangle(100, 600, 200, 20);
        PdfTextFormField phoneField = PdfTextFormField.createText(pdfDoc, rect, "phone", "");
        form.addField(phoneField);
        
        // 添加标签
        Document document = new Document(pdfDoc);
        document.add(new Paragraph("姓名:").setFixedPosition(50, 700, 50));
        document.add(new Paragraph("邮箱:").setFixedPosition(50, 650, 50));
        document.add(new Paragraph("电话:").setFixedPosition(50, 600, 50));
        
        document.close();
        return baos.toByteArray();
    }
}

四、完整的PDF生成Controller

@RestController
@RequestMapping("/api/pdf")
@CrossOrigin(origins = "*")
public class PdfGeneratorController {
    
    @Autowired
    private PdfService pdfService;
    
    @Autowired
    private ChinesePdfService chinesePdfService;
    
    @Autowired
    private TemplatePdfService templatePdfService;

    /**
     * 1. 基础PDF生成接口
     * 生成包含简单文本的PDF文档
     */
    @GetMapping("/basic")
    public ResponseEntity<byte[]> generateBasicPdf() {
        try {
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            pdfService.createSimplePdf(outputStream);
            
            return ResponseEntity.ok()
                .contentType(MediaType.APPLICATION_PDF)
                .header("Content-Disposition", "inline; filename=\"basic-document.pdf\"")
                .body(outputStream.toByteArray());
        } catch (IOException e) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(("PDF生成失败: " + e.getMessage()).getBytes());
        }
    }

    /**
     * 2. 中文PDF生成接口
     * 演示中文字体处理和中文内容支持
     */
    @GetMapping("/chinese")
    public ResponseEntity<byte[]> generateChinesePdf() {
        try {
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            chinesePdfService.createChinesePdf(outputStream);
            
            return ResponseEntity.ok()
                .contentType(MediaType.APPLICATION_PDF)
                .header("Content-Disposition", "inline; filename=\"chinese-document.pdf\"")
                .body(outputStream.toByteArray());
        } catch (IOException e) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(("中文PDF生成失败: " + e.getMessage()).getBytes());
        }
    }

    /**
     * 3. 表格PDF生成接口
     * 展示表格创建和数据展示功能
     */
    @GetMapping("/table")
    public ResponseEntity<byte[]> generateTablePdf() {
        try {
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            pdfService.createTablePdf(outputStream);
            
            return ResponseEntity.ok()
                .contentType(MediaType.APPLICATION_PDF)
                .header("Content-Disposition", "inline; filename=\"table-document.pdf\"")
                .body(outputStream.toByteArray());
        } catch (IOException e) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(("表格PDF生成失败: " + e.getMessage()).getBytes());
        }
    }

    /**
     * 4. 条形码PDF生成接口
     * 演示条形码生成功能(需要barcodes模块)
     */
    @PostMapping("/barcode")
    public ResponseEntity<byte[]> generateBarcodePdf(@RequestParam String barcodeData) {
        try {
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            pdfService.createPdfWithBarcode(outputStream, barcodeData);
            
            return ResponseEntity.ok()
                .contentType(MediaType.APPLICATION_PDF)
                .header("Content-Disposition", "inline; filename=\"barcode-document.pdf\"")
                .body(outputStream.toByteArray());
        } catch (IOException e) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(("条形码PDF生成失败: " + e.getMessage()).getBytes());
        }
    }

    /**
     * 5. 图片PDF生成接口
     * 展示图片和条形码的混合文档
     */
    @PostMapping("/image-barcode")
    public ResponseEntity<byte[]> generateImageBarcodePdf(
            @RequestParam(required = false) String imageUrl,
            @RequestParam String barcodeData) {
        try {
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            pdfService.createPdfWithImageAndBarcode(outputStream, imageUrl, barcodeData);
            
            return ResponseEntity.ok()
                .contentType(MediaType.APPLICATION_PDF)
                .header("Content-Disposition", "inline; filename=\"image-barcode-document.pdf\"")
                .body(outputStream.toByteArray());
        } catch (IOException e) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(("图片条形码PDF生成失败: " + e.getMessage()).getBytes());
        }
    }

    /**
     * 6. 模板PDF生成接口
     * 基于现有PDF模板填充数据
     */
    @PostMapping("/template")
    public ResponseEntity<byte[]> generateTemplatePdf(@RequestBody Map<String, String> formData) {
        try {
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            templatePdfService.fillPdfTemplate(outputStream, formData);
            
            return ResponseEntity.ok()
                .contentType(MediaType.APPLICATION_PDF)
                .header("Content-Disposition", "inline; filename=\"template-document.pdf\"")
                .body(outputStream.toByteArray());
        } catch (IOException e) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(("模板PDF生成失败: " + e.getMessage()).getBytes());
        }
    }

    /**
     * 7. 报告PDF下载接口
     */
    @GetMapping("/download/report")
    public ResponseEntity<byte[]> downloadReportPdf() {
        try {
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            pdfService.createReportPdf(outputStream);
            
            return ResponseEntity.ok()
                .contentType(MediaType.APPLICATION_PDF)
                .header("Content-Disposition", "attachment; filename=\"sales-report.pdf\"")
                .body(outputStream.toByteArray());
        } catch (IOException e) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(("报告PDF下载失败: " + e.getMessage()).getBytes());
        }
    }

    /**
     * 8. 发票PDF下载接口
     */
    @GetMapping("/download/invoice")
    public ResponseEntity<byte[]> downloadInvoicePdf() {
        try {
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            pdfService.createInvoicePdf(outputStream);
            
            return ResponseEntity.ok()
                .contentType(MediaType.APPLICATION_PDF)
                .header("Content-Disposition", "attachment; filename=\"invoice.pdf\"")
                .body(outputStream.toByteArray());
        } catch (IOException e) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(("发票PDF下载失败: " + e.getMessage()).getBytes());
        }
    }
}

五、iText框架优劣分析

优势

  1. 功能全面:支持文本、表格、图片、条形码、表单等几乎所有PDF功能
  2. 性能优秀:处理大型文档时性能表现良好
  3. 标准兼容:完美支持PDF/A、PDF/UA等国际标准
  4. 文档完善:官方文档详细,社区活跃
  5. 企业级支持:提供商业许可证和技术支持

劣势

  1. 学习曲线:API较为复杂,新手需要时间适应
  2. 许可证限制:AGPL协议对商业使用有限制
  3. 内存占用:处理大型文档时内存消耗较高
  4. 配置复杂:模块化设计增加了依赖管理的复杂度

六、不同版本差异对比

特性iText 5.xiText 7.x/9.x说明
架构设计单体架构模块化设计iText 9按功能拆分为多个jar
API设计传统API现代化APIiText 9 API更清晰一致
条形码支持内置核心独立模块iText 9需要单独引入barcodes
性能表现一般优化提升iText 9底层重构,性能更好
学习资源丰富相对较少iText 5教程更多,iText 9较新
维护状态维护模式积极开发iText 9是未来发展方向

七、版本选择建议

选择iText 5.x的情况

选择iText 7.x/9.x的情况

八、最佳实践总结

  1. 依赖管理:仔细选择所需模块,避免引入不必要的依赖
  2. 资源清理:使用try-with-resources确保PDF文档正确关闭
  3. 异常处理:妥善处理IO异常和iText特定异常
  4. 内存管理:对于大文档,考虑分块处理和流式输出
  5. 字体优化:预加载和复用字体对象提升性能

总结

通过本文的完整示例和详细分析,你应该能够在SpringBoot项目中顺利集成iText 9.4.0,并根据具体需求选择合适的版本和功能模块。

iText虽然有一定的学习成本,但其强大的功能和稳定性使其成为企业级PDF处理的优选方案。

iText官方文档https://itextpdf.com/

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

您可能感兴趣的文章:
阅读全文