在实际开发中,pdf生成是常见需求,如报表导出、订单凭证、合同生成等。本文将详细讲解如何基于 spring boot + flying saucer + thymeleaf 实现 pdf 生成,涵盖技术方案解析、核心注解与方法、项目搭建、关键问题解决、功能扩展等关键内容,新手也能快速上手。
本文核心亮点:
- 清晰层次结构:从技术解析到实操落地,逻辑循序渐进
- 核心要点突出:重点讲解核心注解、方法及使用场景
- 代码可复用:提供封装好的工具类,直接复制可用
- 问题导向:针对性解决中文乱码、样式兼容等核心痛点
一、技术方案概述
1.1 核心技术栈
本方案基于三个核心技术组件协同实现 pdf 生成,各组件职责清晰、分工明确,共同完成从数据到 pdf 文档的转换流程:
| 组件 | 作用 | 核心优势 |
| flying saucer(xhtmlrenderer) | 将 html/css 渲染为 pdf | 支持 css 2.1 标准,所见即所得,轻量高效 |
| openpdf | flying saucer 底层 pdf 实现 | 开源免费,支持中文、pdf加密、书签等高级功能 |
| thymeleaf | 动态 html 模板渲染 | 与 spring boot 无缝集成,支持数据绑定、条件渲染 |
1.2 工作原理
核心流程:数据模型 → thymeleaf 模板 → 动态 html → flying saucer 渲染 → pdf 文件
分步解析:
- 数据准备:java 代码中构造需要展示的数据(map、list、自定义对象)
- 模板渲染:thymeleaf 将数据绑定到 html 模板,生成动态 html 字符串
- pdf 转换:flying saucer 解析 html/css,渲染为 pdf 文档
- 输出:将 pdf 写入文件或 http 响应流(支持下载)
二、核心方法详解
核心组件包括 springtemplateengine(thymeleaf 模板渲染核心)和 itextrenderer(flying saucer pdf 渲染核心),其核心方法决定了模板渲染和 pdf 生成的核心逻辑。
2.1 springtemplateengine 核心方法
springtemplateengine 是 thymeleaf 在 spring boot 环境下的核心实现类,负责将模板与数据模型结合生成 html 字符串,核心方法如下:
// 1. 核心渲染方法:将模板与上下文数据结合生成html
string process(string templatename, icontext context);
/* 参数说明:
- templatename:模板名称,默认查找classpath:/templates/目录下的.html文件
- context:上下文对象,存储渲染所需的变量,常用实现类为context
*/
// 示例用法
context context = new context();
context.setvariable("data", reportdata); // 注入数据
string html = templateengine.process("report", context); // 渲染report.html模板
// 2. 清除模板缓存(开发环境常用)
void cleartemplatecache();
// 清除指定模板的缓存
void cleartemplatecachefor(string templatename);
// 3. 检查模板是否缓存
boolean istemplatecached(string templatename);
关键说明:spring boot 会自动装配 springtemplateengine,无需手动创建,可直接通过 @autowired 注入使用;开发环境建议关闭模板缓存(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);
/* 参数说明:
- fontpath:字体文件路径(本地路径或classpath路径)
- encoding:编码格式,basefont.identity_h为unicode编码,支持中文
- embedded:是否嵌入字体到pdf,true则pdf兼容性更好但体积更大,false则体积小需系统有对应字体
*/
// 3. 设置html内容(两种常用方式)
// 方式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表示不结束文档
renderer.writenextdocument(); // 追加下一页
renderer.finishpdf(); // 最终完成文档生成
// 6. 获取共享上下文,用于配置全局参数
sharedcontext sharedcontext = renderer.getsharedcontext();
sharedcontext.setdefaultfont("simhei"); // 设置默认字体
sharedcontext.setmargintop(20); // 设置页面上边距
关键说明:itextrenderer 的使用需遵循"初始化→配置字体→设置文档→布局→生成pdf"的流程;生成多页 pdf 时,需将 createpdf 的 finish 参数设为 false,追加完成后调用 finishpdf() 结束文档。
三、快速上手:项目搭建
掌握核心注解和方法后,即可进行项目搭建。本章节将从项目创建、目录结构、基础配置、启动类编写等方面,完整讲解项目搭建流程。
3.1 创建 spring boot 项目
推荐使用 spring initializr 快速创建,或手动编写 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 项目目录结构
规范目录结构,便于项目维护和扩展:
pdf-generator/
├── src/main/java/com/example/pdf/
│ ├── pdfgeneratorapplication.java # 启动类
│ ├── controller/pdfcontroller.java # 下载接口
│ ├── service/pdfservice.java # 核心服务
│ ├── model/reportdata.java # 数据模型
│ └── utils/pdfutils.java # 工具类
└── src/main/resources/
├── application.yml # 配置文件
├── templates/report.html # thymeleaf模板
└── fonts/simhei.ttf # 中文字体文件
3.3 基础配置(application.yml)
配置服务器端口、thymeleaf 模板参数、pdf 字体相关参数:
server:
port: 8080
spring:
thymeleaf:
cache: false # 开发环境关闭缓存,生产环境开启
mode: html
encoding: utf-8
prefix: classpath:/templates/ # 模板存放路径
suffix: .html
# pdf相关配置 下文示例并未使用
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);
}
}
四、核心实现:从模板到 pdf
项目搭建完成后,即可实现从数据模型定义、模板编写到 pdf 生成的完整核心逻辑。本章节将详细讲解数据模型、thymeleaf 模板、pdf 工具类、控制器的编写。
4.1 数据模型(reportdata.java)
定义需要展示的数据结构,使用 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; // 总操作次数
private string operationtype; // 操作类型
}
4.2 thymeleaf 模板(report.html)
模板即预览,可直接在浏览器中调试样式,注意引入中文字体:
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8"/>
<title>用户操作报告</title>
<style>
/* pdf页面设置: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">统计时间:<span th:text="${data.daterange}">2025-12-01 至 2025-12-31</span></div>
<table>
<thead>
<tr>
<th>用户账号</th>
<th>注册时间</th>
<th>总操作次数</th>
<th>主要操作类型</th>
</tr>
</thead>
<tbody>
<!-- thymeleaf循环渲染数据 -->
<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}">权限管理</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 渲染数据
* @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(调用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并写入响应流(调用itextrenderer的核心方法)
try (outputstream outputstream = response.getoutputstream()) {
itextrenderer renderer = new itextrenderer();
// 加载中文字体(关键:解决中文乱码)
loadchinesefont(renderer);
// 设置html内容
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); // 注入数据
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 接口,供前端调用下载 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. 准备模拟数据(实际开发中从数据库查询)
map<string, object> data = new hashmap<>();
reportdata reportdata = new reportdata();
reportdata.settitle("2025年12月用户高频操作报告");
reportdata.setdaterange("2025-12-01 至 2025-12-31");
// 模拟操作数据
list<useroperation> operations = new arraylist<>();
operations.add(new useroperation("admin001", "2025-11-20", "1200", "权限管理"));
operations.add(new useroperation("user_002", "2025-11-25", "850", "数据查询"));
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);
}
}
五、关键问题解决
在 pdf 生成开发过程中,常会遇到中文乱码、样式不生效、表格分页截断等问题。本章节将针对这些核心痛点,提供具体的解决方案。
5.1 中文乱码问题(核心重点)
flying saucer 默认不支持中文字体,必须手动加载,解决步骤:
下载中文字体文件(如 simhei.ttf 黑体、msyh.ttc 微软雅黑),放入 resources/fonts 目录

在工具类中通过 renderer.getfontresolver().addfont() 加载字体

在 html 模板的 css 中指定字体:font-family: "simhei", sans-serif;

注意:字体路径必须正确,可通过 classpathresource 确保跨环境兼容。
5.2 样式不生效问题
flying saucer 仅支持 css 2.1 标准,不支持 css3 特性(如 flex、grid、border-radius 等),解决方案:
- 使用基础 css 样式(float、position 替代 flex)
- 样式写在
<style>标签内(避免外部 css 文件) - 表格使用
border-collapse: collapse确保边框正常显示
5.3 表格分页截断问题
当表格内容过多跨页时,可能出现行截断,解决方案:
/* 避免表格行被分页截断 */
table {
page-break-inside: avoid;
}
tr {
page-break-inside: avoid;
}
/* 强制分页(如需) */
.page-break {
page-break-after: always;
}
六、进阶功能拓展
基于基础实现,可拓展多页 pdf 生成、图片嵌入、条件渲染等进阶功能,满足更复杂的业务需求。
6.1 嵌入图片到 pdf
支持本地图片、网络图片、base64 图片,示例:
<!-- 本地图片(classpath下) -->
<img src="classpath:images/logo.png" alt="logo" width="100"/>
<!-- 网络图片 -->
<img src="https://example.com/logo.png" alt="logo" width="100"/>
<!-- base64图片(适合动态生成的图片,如二维码) -->
<img th:src="'data:image/png;base64,' + ${qrcodebase64}" alt="二维码"/>
6.2 多页 pdf 生成
如需生成多页 pdf(如多章节报告),可通过 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,不结束文档
// 第二页
string html2 = renderhtml("report-chapter2", data2, templateengine);
renderer.setdocumentfromstring(html2);
renderer.layout();
renderer.writenextdocument(); // 追加下一页
// 结束文档
renderer.finishpdf();
6.3 条件渲染与循环
利用 thymeleaf 语法实现动态逻辑:
<!-- 条件渲染(根据状态显示不同内容) -->
<div th:if="${data.status == 'success'}" style="color: green;">
报告生成成功!
</div>
<div th:unless="${data.status == 'success'}" style="color: red;">
报告生成失败!
</div>
<!-- 循环渲染(带索引) -->
<tr th:each="op, stat : ${data.operations}">
<td th:text="${stat.index + 1}">1</td> <!-- 索引从0开始,+1转为1开始 -->
<td th:text="${op.useraccount}">admin001</td>
</tr>
七、性能优化建议
- 开启模板缓存:生产环境设置
spring.thymeleaf.cache=true,避免重复解析模板 - 字体优化:非特殊需求不嵌入字体(
basefont.not_embedded),减小 pdf 体积 - 异步生成:高并发场景下,使用
@async异步生成 pdf,避免阻塞主线程 - 分批处理:大数据量报表(如10万条数据),建议分页生成后合并 pdf
以上就是springboot+flying saucer+thymeleaf实现pdf生成的完整指南的详细内容,更多关于springboot生成pdf的资料请关注代码网其它相关文章!
发表评论