1. 为什么选择iText7处理PDF
在金融、保险、电商等行业,每天需要处理成千上万的PDF文档。我曾经参与过一个银行电子回单系统改造项目,原先使用POI+PDFBox的方案,生成速度慢且内存占用高,经常导致服务器OOM崩溃。后来切换到iText7后,单文档生成时间从3秒降到300毫秒,内存消耗减少60%。这就是为什么专业场景必须选择iText7。
1.1 企业级PDF的核心需求
典型业务场景分析:
-
电子合同场景
以保险行业为例,每份保单需要包含:- 动态填充的投保人信息
- 条款内容自动分页
- 数字签名防篡改
- 多语言支持(特别是中英文混排)
-
账单回单场景
银行流水PDF的特殊要求:- 表格数据可能超过1000行
- 需要分页续表(表头每页重复)
- 金额数字需要特殊字体防篡改
- 二维码自动生成并嵌入
-
报表导出场景
电商运营报表的痛点:- 复杂图表与表格混合排版
- 页眉页脚需要动态页码
- 导出时可能超过100页
- 需要支持后台批量生成
1.2 iText7的独特优势
对比其他PDF库,iText7在以下方面表现突出:
| 特性 | iText7 | PDFBox | Apache FOP |
|---|---|---|---|
| 大文件处理能力 | ✅ | ❌ | ⚠️ |
| 内存管理 | 分块处理 | 全加载 | 全加载 |
| 表格渲染精度 | 像素级 | 基础对齐 | 依赖XSL-FO |
| 数字签名支持 | 完整链 | 仅基础 | 不支持 |
| 中文字体处理 | 原生支持 | 需hack | 配置复杂 |
实际测试数据:生成100页含中文表格的PDF,iText7耗时1.2秒,内存峰值80MB;PDFBox耗时4.3秒,内存峰值320MB
2. 环境准备与字体配置
2.1 依赖配置最佳实践
使用Maven时推荐这样引入(示例为7.2.5版本):
xml复制<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itext7-core</artifactId>
<version>7.2.5</version>
<type>pom</type>
</dependency>
<!-- 必须单独添加的中文模块 -->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>font-asian</artifactId>
<version>7.2.5</version>
</dependency>
关键注意事项:
- 永远不要混用不同版本的jar包
- 商业项目注意AGPL协议限制
- Linux环境需要额外安装字体
2.2 中文字体处理方案
Windows环境配置:
java复制// 推荐使用思源黑体
PdfFont font = PdfFontFactory.createFont(
"C:/Windows/Fonts/NotoSansCJKsc-Regular.otf",
PdfEncodings.IDENTITY_H,
PdfFontFactory.EmbeddingStrategy.PREFER_EMBEDDED);
Linux服务器配置步骤:
- 将字体文件上传到
/usr/share/fonts/chinese/ - 执行权限设置:
bash复制sudo mkdir -p /usr/share/fonts/chinese sudo chmod 755 /usr/share/fonts/chinese - 刷新字体缓存:
bash复制
fc-cache -fv
常见踩坑点:
- 缺少
PdfEncodings.IDENTITY_H参数会导致中文显示为空白 - 未嵌入字体的PDF在其他设备可能显示异常
- 商用字体需注意版权问题
3. PDF核心操作实战
3.1 文档创建基础流程
iText7的三层架构设计:
- Document层 - 文档容器
- PdfDocument层 - PDF核心操作
- PdfWriter层 - 输出控制
完整创建示例:
java复制// 输出到字节数组(适合Web应用)
ByteArrayOutputStream baos = new ByteArrayOutputStream();
PdfWriter writer = new PdfWriter(baos);
PdfDocument pdf = new PdfDocument(writer);
Document document = new Document(pdf);
// 设置文档属性
pdf.getDocumentInfo()
.setTitle("电子合同")
.setAuthor("财务系统");
// 添加内容段落
document.add(new Paragraph("合同正文").setFont(font));
// 必须显式关闭
document.close();
byte[] pdfBytes = baos.toByteArray();
关键技巧:
- 使用try-with-resources确保资源释放
- 输出到ByteArrayOutputStream适合Web场景
- 文档属性有助于后续检索管理
3.2 复杂表格生成
金融行业账单表格的典型实现:
java复制// 创建带边框的表格(5列)
Table table = new Table(UnitValue.createPercentArray(5))
.setWidth(UnitValue.createPercentValue(100));
// 添加表头
for (String header : Arrays.asList("日期", "摘要", "收入", "支出", "余额")) {
table.addHeaderCell(new Cell().add(new Paragraph(header)));
}
// 模拟数据行
for (int i = 0; i < 150; i++) {
table.addCell(new Cell().add(new Paragraph("2023-01-" + (i%30+1))));
table.addCell(new Cell().add(new Paragraph("交易" + i)));
table.addCell(new Cell().add(new Paragraph(i % 5 == 0 ? String.valueOf(i*100) : "")));
table.addCell(new Cell().add(new Paragraph(i % 5 != 0 ? String.valueOf(i*50) : "")));
table.addCell(new Cell().add(new Paragraph(String.valueOf(10000 - i*20))));
// 自动分页处理
if (i % 40 == 0 && i != 0) {
table.startNewRow();
for (int j = 0; j < 5; j++) {
table.addHeaderCell(new Cell().add(new Paragraph("续表")));
}
}
}
document.add(table);
表格优化技巧:
- 使用
UnitValue.createPercentArray实现响应式列宽 addHeaderCell方法实现跨页表头重复- 大数据量时每50行添加续表提示
- 金额列建议使用等宽数字字体
4. 高级功能实现
4.1 数字签名实战
法律效力的PDF签名需要以下步骤:
- 准备PKCS12证书文件
- 创建签名外观
- 设置签名属性
java复制// 加载证书
String certPath = "/security/company.p12";
char[] password = "123456".toCharArray();
PdfSigner signer = new PdfSigner(
new PdfReader(inputPdf),
new FileOutputStream(outputPdf),
new StampingProperties());
// 设置签名外观
Rectangle rect = new Rectangle(100, 100, 200, 100);
signer.setFieldName("signature1");
PdfSignatureAppearance appearance = signer.getSignatureAppearance()
.setReason("合同签署")
.setLocation("北京")
.setPageRect(rect)
.setPageNumber(1);
// 创建签名工具
IExternalSignature pks = new PrivateKeySignature(
Pkcs12FileHelper.readFirstPrivateKey(certPath, password),
DigestAlgorithms.SHA256);
signer.signDetached(pks,
new Certificate[0],
null,
null,
null,
0,
PdfSigner.CryptoStandard.CMS);
签名注意事项:
- 证书有效期需要检查
- 时间戳服务增加法律效力
- 签名后文档变为只读
- 建议保留签名日志
4.2 条形码与二维码
物流单据常用条码集成方案:
java复制// 创建二维码
BarcodeQRCode qrCode = new BarcodeQRCode("https://example.com/track/123");
Image qrImage = new Image(qrCode.createFormXObject(pdf));
// 创建条形码
Barcode128 barCode = new Barcode128(pdf);
barCode.setCode("ITEM20230001");
Image barImage = new Image(barCode.createFormXObject(pdf));
// 并排显示
Div div = new Div()
.add(qrImage.setWidth(100).setHeight(100))
.add(barImage.setWidth(200).setHeight(50))
.setTextAlignment(TextAlignment.CENTER);
document.add(div);
5. 性能优化与问题排查
5.1 内存泄漏预防
通过内存分析工具发现常见问题:
-
未关闭Document对象
java复制// 错误示例(会导致文件锁和内存泄漏) new Document(new PdfDocument(new PdfWriter("temp.pdf"))); // 正确做法 try (Document doc = new Document(...)) { // 操作文档 } -
大图片未压缩
java复制ImageData imageData = ImageDataFactory.create(imgPath); imageData.setCompressionLevel(9); // 0-9压缩级别 -
字体重复加载
应该全局缓存字体对象:java复制public class FontHolder { private static PdfFont mainFont; public static PdfFont getMainFont() { if (mainFont == null) { mainFont = PdfFontFactory.createFont(...); } return mainFont; } }
5.2 常见问题速查表
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 中文显示为空白 | 未正确设置IDENTITY_H编码 | 检查PdfFontFactory.create参数 |
| 表格跨页排版错乱 | 未使用addHeaderCell | 确保表头单元格使用正确方法 |
| 签名后内容无法修改 | 签名时设置了Certification | 使用非Certification模式 |
| 生成速度越来越慢 | 未复用字体/样式对象 | 实现对象池模式 |
| 服务器内存溢出 | 未使用分页处理大数据 | 实现PdfDocument分批处理 |
在电商大促期间,我们通过以下配置实现万级PDF生成:
- 使用Redis缓存常用模板
- 预编译所有样式对象
- 采用零拷贝输出流
- 限制单个JVM进程并发任务数