SpringBoot+Flying Saucer+Thymeleaf實現(xiàn)PDF生成的完整指南
在實際開發(fā)中,PDF生成是常見需求,如報表導出、訂單憑證、合同生成等。本文將詳細講解如何基于 Spring Boot + Flying Saucer + Thymeleaf 實現(xiàn) PDF 生成,涵蓋技術(shù)方案解析、核心注解與方法、項目搭建、關(guān)鍵問題解決、功能擴展等關(guān)鍵內(nèi)容,新手也能快速上手。
本文核心亮點:
- 清晰層次結(jié)構(gòu):從技術(shù)解析到實操落地,邏輯循序漸進
- 核心要點突出:重點講解核心注解、方法及使用場景
- 代碼可復用:提供封裝好的工具類,直接復制可用
- 問題導向:針對性解決中文亂碼、樣式兼容等核心痛點
一、技術(shù)方案概述
1.1 核心技術(shù)棧
本方案基于三個核心技術(shù)組件協(xié)同實現(xiàn) PDF 生成,各組件職責清晰、分工明確,共同完成從數(shù)據(jù)到 PDF 文檔的轉(zhuǎn)換流程:
| 組件 | 作用 | 核心優(yōu)勢 |
| Flying Saucer(xhtmlrenderer) | 將 HTML/CSS 渲染為 PDF | 支持 CSS 2.1 標準,所見即所得,輕量高效 |
| OpenPDF | Flying Saucer 底層 PDF 實現(xiàn) | 開源免費,支持中文、PDF加密、書簽等高級功能 |
| Thymeleaf | 動態(tài) HTML 模板渲染 | 與 Spring Boot 無縫集成,支持數(shù)據(jù)綁定、條件渲染 |
1.2 工作原理
核心流程:數(shù)據(jù)模型 → Thymeleaf 模板 → 動態(tài) HTML → Flying Saucer 渲染 → PDF 文件
分步解析:
- 數(shù)據(jù)準備:Java 代碼中構(gòu)造需要展示的數(shù)據(jù)(Map、List、自定義對象)
- 模板渲染:Thymeleaf 將數(shù)據(jù)綁定到 HTML 模板,生成動態(tài) HTML 字符串
- PDF 轉(zhuǎn)換:Flying Saucer 解析 HTML/CSS,渲染為 PDF 文檔
- 輸出:將 PDF 寫入文件或 HTTP 響應流(支持下載)
二、核心方法詳解
核心組件包括 SpringTemplateEngine(Thymeleaf 模板渲染核心)和 ITextRenderer(Flying Saucer PDF 渲染核心),其核心方法決定了模板渲染和 PDF 生成的核心邏輯。
2.1 SpringTemplateEngine 核心方法
SpringTemplateEngine 是 Thymeleaf 在 Spring Boot 環(huán)境下的核心實現(xiàn)類,負責將模板與數(shù)據(jù)模型結(jié)合生成 HTML 字符串,核心方法如下:
// 1. 核心渲染方法:將模板與上下文數(shù)據(jù)結(jié)合生成HTML
String process(String templateName, IContext context);
/* 參數(shù)說明:
- templateName:模板名稱,默認查找classpath:/templates/目錄下的.html文件
- context:上下文對象,存儲渲染所需的變量,常用實現(xiàn)類為Context
*/
// 示例用法
Context context = new Context();
context.setVariable("data", reportData); // 注入數(shù)據(jù)
String html = templateEngine.process("report", context); // 渲染report.html模板
// 2. 清除模板緩存(開發(fā)環(huán)境常用)
void clearTemplateCache();
// 清除指定模板的緩存
void clearTemplateCacheFor(String templateName);
// 3. 檢查模板是否緩存
boolean isTemplateCached(String templateName);
關(guān)鍵說明:Spring Boot 會自動裝配 SpringTemplateEngine,無需手動創(chuàng)建,可直接通過 @Autowired 注入使用;開發(fā)環(huán)境建議關(guān)閉模板緩存(spring.thymeleaf.cache=false),避免修改模板后需重啟項目。
2.2 ITextRenderer 核心方法
ITextRenderer 是 Flying Saucer 的核心類,負責將 HTML/CSS 渲染為 PDF,核心方法如下:
// 1. 初始化渲染器
ITextRenderer renderer = new ITextRenderer();
// 2. 加載中文字體(解決中文亂碼核心方法)
ITextFontResolver fontResolver = renderer.getFontResolver();
fontResolver.addFont(String fontPath, String encoding, boolean embedded);
/* 參數(shù)說明:
- fontPath:字體文件路徑(本地路徑或classpath路徑)
- encoding:編碼格式,BaseFont.IDENTITY_H為Unicode編碼,支持中文
- embedded:是否嵌入字體到PDF,true則PDF兼容性更好但體積更大,false則體積小需系統(tǒng)有對應字體
*/
// 3. 設(shè)置HTML內(nèi)容(兩種常用方式)
// 方式1:從HTML字符串加載
renderer.setDocumentFromString(String html);
// 方式2:從文件/URL加載
renderer.setDocument(File file);
renderer.setDocument(URL url);
// 4. 布局計算:解析HTML/CSS并計算元素位置
renderer.layout();
// 5. 生成PDF(兩種常用方式)
// 方式1:寫入輸出流(響應下載常用)
renderer.createPDF(OutputStream os);
// 方式2:分頁生成多PDF(需追加頁面時使用)
renderer.createPDF(OutputStream os, boolean finish); // finish=false表示不結(jié)束文檔
renderer.writeNextDocument(); // 追加下一頁
renderer.finishPDF(); // 最終完成文檔生成
// 6. 獲取共享上下文,用于配置全局參數(shù)
SharedContext sharedContext = renderer.getSharedContext();
sharedContext.setDefaultFont("SimHei"); // 設(shè)置默認字體
sharedContext.setMarginTop(20); // 設(shè)置頁面上邊距
關(guān)鍵說明:ITextRenderer 的使用需遵循"初始化→配置字體→設(shè)置文檔→布局→生成PDF"的流程;生成多頁 PDF 時,需將 createPDF 的 finish 參數(shù)設(shè)為 false,追加完成后調(diào)用 finishPDF() 結(jié)束文檔。
三、快速上手:項目搭建
掌握核心注解和方法后,即可進行項目搭建。本章節(jié)將從項目創(chuàng)建、目錄結(jié)構(gòu)、基礎(chǔ)配置、啟動類編寫等方面,完整講解項目搭建流程。
3.1 創(chuàng)建 Spring Boot 項目
推薦使用 Spring Initializr 快速創(chuàng)建,或手動編寫 pom.xml。
核心依賴(pom.xml)
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version>
</parent>
<dependencies>
<!-- Spring Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Thymeleaf -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- Flying Saucer + OpenPDF -->
<dependency>
<groupId>org.xhtmlrenderer</groupId>
<artifactId>flying-saucer-pdf-openpdf</artifactId>
<version>9.1.22</version>
</dependency>
<!-- Lombok(簡化代碼,可選) -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>3.2 項目目錄結(jié)構(gòu)
規(guī)范目錄結(jié)構(gòu),便于項目維護和擴展:
pdf-generator/
├── src/main/java/com/example/pdf/
│ ├── PdfGeneratorApplication.java # 啟動類
│ ├── controller/PdfController.java # 下載接口
│ ├── service/PdfService.java # 核心服務(wù)
│ ├── model/ReportData.java # 數(shù)據(jù)模型
│ └── utils/PdfUtils.java # 工具類
└── src/main/resources/
├── application.yml # 配置文件
├── templates/report.html # Thymeleaf模板
└── fonts/SimHei.ttf # 中文字體文件
3.3 基礎(chǔ)配置(application.yml)
配置服務(wù)器端口、Thymeleaf 模板參數(shù)、PDF 字體相關(guān)參數(shù):
server:
port: 8080
spring:
thymeleaf:
cache: false # 開發(fā)環(huán)境關(guān)閉緩存,生產(chǎn)環(huán)境開啟
mode: HTML
encoding: UTF-8
prefix: classpath:/templates/ # 模板存放路徑
suffix: .html
# PDF相關(guān)配置 下文示例并未使用
pdf:
font:
path: classpath:fonts/SimHei.ttf # 字體路徑
embedded: false # 是否嵌入字體(嵌入后PDF更大,兼容性更好)
3.4 啟動類
編寫 Spring Boot 應用入口類,用于啟動應用:
package com.example.pdf;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class PdfGeneratorApplication {
public static void main(String[] args) {
SpringApplication.run(PdfGeneratorApplication.class, args);
}
}
四、核心實現(xiàn):從模板到 PDF
項目搭建完成后,即可實現(xiàn)從數(shù)據(jù)模型定義、模板編寫到 PDF 生成的完整核心邏輯。本章節(jié)將詳細講解數(shù)據(jù)模型、Thymeleaf 模板、PDF 工具類、控制器的編寫。
4.1 數(shù)據(jù)模型(ReportData.java)
定義需要展示的數(shù)據(jù)結(jié)構(gòu),使用 Lombok 簡化 getter/setter:
package com.example.pdf.model;
import lombok.Data;
import java.util.List;
@Data
public class ReportData {
private String title; // 報告標題
private String dateRange; // 日期范圍
private List<UserOperation> operations; // 操作列表
}
// 子模型
@Data
public class UserOperation {
private String userAccount; // 用戶賬號
private String registrationDate;// 注冊時間
private String totalOperations; // 總操作次數(shù)
private String operationType; // 操作類型
}
4.2 Thymeleaf 模板(report.html)
模板即預覽,可直接在瀏覽器中調(diào)試樣式,注意引入中文字體:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"/>
<title>用戶操作報告</title>
<style>
/* PDF頁面設(shè)置:A4橫向,邊距20mm */
@page {
size: A4 landscape;
margin: 20mm;
}
/* 全局樣式,指定中文字體 */
body {
font-family: "SimHei", "Microsoft YaHei", sans-serif;
font-size: 14px;
color: #333;
}
.title {
font-size: 24px;
font-weight: bold;
text-align: center;
margin-bottom: 20px;
}
.meta {
font-size: 14px;
margin-bottom: 15px;
color: #666;
}
/* 表格樣式 */
table {
width: 100%;
border-collapse: collapse;
}
th, td {
border: 1px solid #ccc;
padding: 8px 10px;
text-align: center;
}
th {
background-color: #f5f5f5;
font-weight: bold;
}
</style>
</head>
<body>
<div class="title" th:text="${data.title}">用戶高頻操作報告</div>
<div class="meta">統(tǒng)計時間:<span th:text="${data.dateRange}">2025-12-01 至 2025-12-31</span></div>
<table>
<thead>
<tr>
<th>用戶賬號</th>
<th>注冊時間</th>
<th>總操作次數(shù)</th>
<th>主要操作類型</th>
</tr>
</thead>
<tbody>
<!-- Thymeleaf循環(huán)渲染數(shù)據(jù) -->
<tr th:each="op : ${data.operations}">
<td th:text="${op.userAccount}">admin001</td>
<td th:text="${op.registrationDate}">2025-11-20</td>
<td th:text="${op.totalOperations}">1000</td>
<td th:text="${op.operationType}">權(quán)限管理</td>
</tr>
</tbody>
</table>
</body>
</html>4.3 PDF 工具類(PdfUtils.java)
封裝 HTML 渲染和 PDF 生成邏輯,核心工具類,可直接復用:
package com.example.pdf.utils;
import com.lowagie.text.pdf.BaseFont;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.core.io.ClassPathResource;
import org.thymeleaf.context.Context;
import org.thymeleaf.spring6.SpringTemplateEngine;
import org.xhtmlrenderer.pdf.ITextRenderer;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Map;
public class PdfUtils {
/**
* 生成PDF并響應給前端(下載)
* @param templateName 模板名稱
* @param data 渲染數(shù)據(jù)
* @param response 響應對象
* @param fileName 下載文件名
* @param templateEngine Thymeleaf引擎
*/
public static void generatePdfForDownload(String templateName, Map<String, Object> data,
HttpServletResponse response, String fileName,
SpringTemplateEngine templateEngine) throws Exception {
// 1. 渲染HTML(調(diào)用SpringTemplateEngine的process方法)
String html = renderHtml(templateName, data, templateEngine);
// 2. 響應配置
response.setContentType("application/pdf");
response.setHeader("Content-Disposition",
"attachment; filename=\"" + URLEncoder.encode(fileName, StandardCharsets.UTF_8) + ".pdf\"");
response.setHeader("Cache-Control", "no-cache, no-store");
// 3. 生成PDF并寫入響應流(調(diào)用ITextRenderer的核心方法)
try (OutputStream outputStream = response.getOutputStream()) {
ITextRenderer renderer = new ITextRenderer();
// 加載中文字體(關(guān)鍵:解決中文亂碼)
loadChineseFont(renderer);
// 設(shè)置HTML內(nèi)容
renderer.setDocumentFromString(html);
// 布局計算
renderer.layout();
// 生成PDF
renderer.createPDF(outputStream);
}
}
/**
* 渲染Thymeleaf模板為HTML字符串
*/
private static String renderHtml(String templateName, Map<String, Object> data, SpringTemplateEngine templateEngine) {
Context context = new Context();
context.setVariables(data); // 注入數(shù)據(jù)
return templateEngine.process(templateName, context); // 核心渲染方法
}
/**
* 加載中文字體
*/
private static void loadChineseFont(ITextRenderer renderer) throws Exception {
// 從classpath加載字體文件
ClassPathResource fontResource = new ClassPathResource("fonts/SimHei.ttf");
String fontPath = fontResource.getURL().toString();
// 添加字體到渲染器(解決中文亂碼核心方法)
renderer.getFontResolver().addFont(
fontPath,
BaseFont.IDENTITY_H, // Unicode編碼(支持中文)
BaseFont.NOT_EMBEDDED // 不嵌入字體(減小PDF體積)
);
}
}
4.4 控制器(PdfController.java)
提供 HTTP 接口,供前端調(diào)用下載 PDF:
package com.example.pdf.controller;
import com.example.pdf.model.ReportData;
import com.example.pdf.model.UserOperation;
import com.example.pdf.utils.PdfUtils;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.thymeleaf.spring6.SpringTemplateEngine;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api/pdf")
public class PdfController {
@Autowired
private SpringTemplateEngine templateEngine; // 注入模板引擎
/**
* 下載用戶操作報告PDF
*/
@GetMapping("/download/report")
public void downloadReport(HttpServletResponse response) throws Exception {
// 1. 準備模擬數(shù)據(jù)(實際開發(fā)中從數(shù)據(jù)庫查詢)
Map<String, Object> data = new HashMap<>();
ReportData reportData = new ReportData();
reportData.setTitle("2025年12月用戶高頻操作報告");
reportData.setDateRange("2025-12-01 至 2025-12-31");
// 模擬操作數(shù)據(jù)
List<UserOperation> operations = new ArrayList<>();
operations.add(new UserOperation("admin001", "2025-11-20", "1200", "權(quán)限管理"));
operations.add(new UserOperation("user_002", "2025-11-25", "850", "數(shù)據(jù)查詢"));
operations.add(new UserOperation("manager_003", "2025-12-01", "680", "報表導出"));
reportData.setOperations(operations);
data.put("data", reportData);
// 2. 生成PDF并下載
String fileName = "用戶操作報告_" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
PdfUtils.generatePdfForDownload("report", data, response, fileName, templateEngine);
}
}
五、關(guān)鍵問題解決
在 PDF 生成開發(fā)過程中,常會遇到中文亂碼、樣式不生效、表格分頁截斷等問題。本章節(jié)將針對這些核心痛點,提供具體的解決方案。
5.1 中文亂碼問題(核心重點)
Flying Saucer 默認不支持中文字體,必須手動加載,解決步驟:
下載中文字體文件(如 SimHei.ttf 黑體、msyh.ttc 微軟雅黑),放入 resources/fonts 目錄

在工具類中通過 renderer.getFontResolver().addFont() 加載字體

在 HTML 模板的 CSS 中指定字體:font-family: "SimHei", sans-serif;

注意:字體路徑必須正確,可通過 ClassPathResource 確??绛h(huán)境兼容。
5.2 樣式不生效問題
Flying Saucer 僅支持 CSS 2.1 標準,不支持 CSS3 特性(如 flex、grid、border-radius 等),解決方案:
- 使用基礎(chǔ) CSS 樣式(float、position 替代 flex)
- 樣式寫在
<style>標簽內(nèi)(避免外部 CSS 文件) - 表格使用
border-collapse: collapse確保邊框正常顯示
5.3 表格分頁截斷問題
當表格內(nèi)容過多跨頁時,可能出現(xiàn)行截斷,解決方案:
/* 避免表格行被分頁截斷 */
table {
page-break-inside: avoid;
}
tr {
page-break-inside: avoid;
}
/* 強制分頁(如需) */
.page-break {
page-break-after: always;
}
六、進階功能拓展
基于基礎(chǔ)實現(xiàn),可拓展多頁 PDF 生成、圖片嵌入、條件渲染等進階功能,滿足更復雜的業(yè)務(wù)需求。
6.1 嵌入圖片到 PDF
支持本地圖片、網(wǎng)絡(luò)圖片、Base64 圖片,示例:
<!-- 本地圖片(classpath下) -->
<img src="classpath:images/logo.png" alt="logo" width="100"/>
<!-- 網(wǎng)絡(luò)圖片 -->
<img src="https://example.com/logo.png" alt="logo" width="100"/>
<!-- Base64圖片(適合動態(tài)生成的圖片,如二維碼) -->
<img th:src="'data:image/png;base64,' + ${qrCodeBase64}" alt="二維碼"/>
6.2 多頁 PDF 生成
如需生成多頁 PDF(如多章節(jié)報告),可通過 writeNextDocument()追加頁面:
// 多頁PDF生成核心代碼
ITextRenderer renderer = new ITextRenderer();
loadChineseFont(renderer);
// 第一頁
String html1 = renderHtml("report-chapter1", data1, templateEngine);
renderer.setDocumentFromString(html1);
renderer.layout();
renderer.createPDF(outputStream, false); // finish=false,不結(jié)束文檔
// 第二頁
String html2 = renderHtml("report-chapter2", data2, templateEngine);
renderer.setDocumentFromString(html2);
renderer.layout();
renderer.writeNextDocument(); // 追加下一頁
// 結(jié)束文檔
renderer.finishPDF();
6.3 條件渲染與循環(huán)
利用 Thymeleaf 語法實現(xiàn)動態(tài)邏輯:
<!-- 條件渲染(根據(jù)狀態(tài)顯示不同內(nèi)容) -->
<div th:if="${data.status == 'success'}" style="color: green;">
報告生成成功!
</div>
<div th:unless="${data.status == 'success'}" style="color: red;">
報告生成失??!
</div>
<!-- 循環(huán)渲染(帶索引) -->
<tr th:each="op, stat : ${data.operations}">
<td th:text="${stat.index + 1}">1</td> <!-- 索引從0開始,+1轉(zhuǎn)為1開始 -->
<td th:text="${op.userAccount}">admin001</td>
</tr>
七、性能優(yōu)化建議
- 開啟模板緩存:生產(chǎn)環(huán)境設(shè)置
spring.thymeleaf.cache=true,避免重復解析模板 - 字體優(yōu)化:非特殊需求不嵌入字體(
BaseFont.NOT_EMBEDDED),減小 PDF 體積 - 異步生成:高并發(fā)場景下,使用
@Async異步生成 PDF,避免阻塞主線程 - 分批處理:大數(shù)據(jù)量報表(如10萬條數(shù)據(jù)),建議分頁生成后合并 PDF
以上就是SpringBoot+Flying Saucer+Thymeleaf實現(xiàn)PDF生成的完整指南的詳細內(nèi)容,更多關(guān)于SpringBoot生成PDF的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java多線程并發(fā)生產(chǎn)者消費者設(shè)計模式實例解析
這篇文章主要介紹了Java多線程并發(fā)生產(chǎn)者消費者設(shè)計模式實例解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2020-03-03
Spring Cloud Feign實現(xiàn)動態(tài)URL
本文主要介紹了Spring Cloud Feign實現(xiàn)動態(tài)URL,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-02-02
Java向數(shù)據(jù)庫中插入數(shù)據(jù)后獲取自增ID的常用方法
有時候因為新增的需求需要獲取剛剛新增的數(shù)據(jù)的自增的主鍵ID,下面這篇文章主要給大家介紹了關(guān)于Java向數(shù)據(jù)庫中插入數(shù)據(jù)后獲取自增ID的常用方法,文中通過代碼介紹的非常詳細,需要的朋友可以參考下2023-11-11
java?fastjson傳輸long數(shù)據(jù)卻接收到了int的問題
這篇文章主要介紹了java?fastjson傳輸long數(shù)據(jù)卻接收到了int的問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-01-01

