1. 项目概述
在Java企业级开发中,PDF导出功能是常见的业务需求。Spring Boot作为当前最流行的Java应用框架,提供了便捷的PDF生成方案。本文将详细介绍三种主流的Spring Boot PDF导出实现方式,包含完整代码示例和性能对比。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术方案选型
2.1 主流PDF生成库对比
| 技术方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| iText | 功能强大,支持复杂排版 | 商业授权复杂,学习曲线陡 | 需要精确控制版面的场景 |
| Apache PDFBox | 完全开源,支持PDF解析 | 生成复杂文档时代码量大 | 需要读写PDF文件的场景 |
| Flying Saucer | HTML转PDF,开发简单 | 中文支持需要额外配置 | 已有HTML模板的场景 |
提示:iText 7.x版本采用AGPL协议,商业项目需要购买商业授权。PDFBox和Flying Saucer均为Apache协议,可自由使用。
2.2 环境准备
xml复制<!-- PDFBox依赖 -->
<dependency>
<groupId>org.apache.pdfbox</groupId>
<artifactId>pdfbox</artifactId>
<version>2.0.27</version>
</dependency>
<!-- Flying Saucer依赖 -->
<dependency>
<groupId>org.xhtmlrenderer</groupId>
<artifactId>flying-saucer-pdf</artifactId>
<version>9.1.22</version>
</dependency>
3. 核心实现方案
3.1 使用PDFBox生成PDF
java复制@RestController
public class PdfController {
@GetMapping("/export/pdfbox")
public void exportPdf(HttpServletResponse response) throws IOException {
PDDocument document = new PDDocument();
PDPage page = new PDPage();
document.addPage(page);
try (PDPageContentStream contentStream = new PDPageContentStream(document, page)) {
contentStream.setFont(PDType1Font.HELVETICA_BOLD, 12);
contentStream.beginText();
contentStream.newLineAtOffset(100, 700);
contentStream.showText("Spring Boot PDF Export Demo");
contentStream.endText();
}
response.setContentType("application/pdf");
response.setHeader("Content-Disposition", "attachment; filename=export.pdf");
document.save(response.getOutputStream());
document.close();
}
}
3.2 使用Flying Saucer实现HTML转PDF
java复制@GetMapping("/export/flying-saucer")
public void exportHtmlToPdf(HttpServletResponse response) throws Exception {
String html = "<html><body><h1>Spring Boot PDF 导出</h1></body></html>";
ITextRenderer renderer = new ITextRenderer();
renderer.setDocumentFromString(html);
renderer.layout();
response.setContentType("application/pdf");
response.setHeader("Content-Disposition", "attachment; filename=export.pdf");
renderer.createPDF(response.getOutputStream());
renderer.finishPDF();
}
4. 高级功能实现
4.1 中文支持解决方案
对于Flying Saucer方案,需要额外配置中文字体:
java复制// 字体配置示例
ITextRenderer renderer = new ITextRenderer();
renderer.getFontResolver().addFont(
"src/main/resources/fonts/simhei.ttf",
BaseFont.IDENTITY_H,
BaseFont.EMBEDDED
);
4.2 表格数据导出
使用PDFBox创建表格的实用方法:
java复制private void drawTable(PDPage page, PDDocument document, float y) throws IOException {
float margin = 50;
float tableWidth = page.getMediaBox().getWidth() - 2 * margin;
float rowHeight = 20f;
float cellMargin = 5f;
// 表头绘制
try (PDPageContentStream contentStream = new PDPageContentStream(document, page, true, true)) {
contentStream.setFont(PDType1Font.HELVETICA_BOLD, 12);
contentStream.setNonStrokingColor(Color.LIGHT_GRAY);
contentStream.addRect(margin, y, tableWidth, rowHeight);
contentStream.fill();
// 表头文本
contentStream.setNonStrokingColor(Color.BLACK);
contentStream.beginText();
contentStream.newLineAtOffset(margin + cellMargin, y + cellMargin);
contentStream.showText("ID");
contentStream.endText();
}
}
5. 性能优化建议
- 对象复用:PDFBox的PDDocument对象创建成本高,应考虑复用
- 批量处理:大批量生成时使用PdfMerger合并文档
- 内存管理:大于10MB的PDF应考虑使用临时文件
- 异步导出:耗时操作应使用@Async注解
java复制@Async
public CompletableFuture<ByteArrayResource> asyncExport() {
// 异步导出逻辑
}
6. 常见问题排查
6.1 中文乱码问题
现象:PDF中中文显示为方框
解决方案:
- 确认字体文件路径正确
- 检查字体是否支持目标字符集
- 确保字体已嵌入PDF
6.2 内存溢出问题
现象:生成大文件时OOM
解决方案:
- 增加JVM内存:-Xmx1024m
- 使用文件缓存代替内存操作
- 分页处理大数据集
6.3 样式不一致问题
现象:HTML转PDF后样式错乱
解决方案:
- 使用内联样式代替外部CSS
- 避免使用浮动布局
- 明确指定所有元素的尺寸
7. 扩展应用场景
7.1 与Thymeleaf模板集成
java复制@Autowired
private SpringTemplateEngine templateEngine;
public String renderTemplate(Map<String, Object> variables) {
Context context = new Context();
context.setVariables(variables);
return templateEngine.process("pdf-template", context);
}
7.2 动态水印添加
java复制public void addWatermark(PDDocument document, String text) throws IOException {
for (PDPage page : document.getPages()) {
PDPageContentStream contentStream = new PDPageContentStream(
document, page, PDPageContentStream.AppendMode.APPEND, true);
contentStream.setFont(PDType1Font.HELVETICA, 36);
contentStream.setNonStrokingColor(200, 200, 200);
// 旋转45度写入水印
contentStream.beginText();
contentStream.setTextRotation(Math.PI / 4, 100, 100);
contentStream.showText(text);
contentStream.endText();
contentStream.close();
}
}
8. 安全注意事项
- 文件上传防护:处理用户上传的PDF模板时需验证内容
- XSS防护:动态生成HTML内容时需转义特殊字符
- 敏感信息:确保生成的PDF不包含未授权信息
- 访问控制:导出接口应添加权限校验
java复制@PreAuthorize("hasRole('EXPORT')")
@GetMapping("/secure/export")
public void secureExport(HttpServletResponse response) {
// 安全导出逻辑
}
9. 测试方案设计
9.1 单元测试示例
java复制@Test
public void testPdfGeneration() throws Exception {
MockHttpServletResponse response = new MockHttpServletResponse();
pdfController.exportPdf(response);
assertEquals("application/pdf", response.getContentType());
assertTrue(response.getContentAsByteArray().length > 0);
// 验证PDF内容
try (PDDocument doc = PDDocument.load(response.getContentAsByteArray())) {
assertEquals(1, doc.getNumberOfPages());
}
}
9.2 性能测试建议
- 单次生成耗时应<500ms(普通文档)
- 内存占用应<文档大小的3倍
- 并发测试建议使用JMeter模拟
10. 部署优化
- Docker配置:增加字体支持的基础镜像
dockerfile复制FROM openjdk:11-jre
RUN apt-get update && apt-get install -y fonts-wqy-zenhei
COPY target/app.jar /app.jar
- Kubernetes资源限制:
yaml复制resources:
limits:
memory: "1Gi"
requests:
memory: "512Mi"
- 健康检查端点:
java复制@GetMapping("/actuator/pdf-health")
public Health pdfHealth() {
// 检查字体文件等依赖资源
}
