当前位置: 代码网 > it编程>编程语言>Java > SpringBoot+Flying Saucer+Thymeleaf实现PDF生成的完整指南

SpringBoot+Flying Saucer+Thymeleaf实现PDF生成的完整指南

2026年01月07日 Java 我要评论
在实际开发中,pdf生成是常见需求,如报表导出、订单凭证、合同生成等。本文将详细讲解如何基于 spring boot + flying saucer + thymeleaf 实现 pdf 生成,涵盖技

在实际开发中,pdf生成是常见需求,如报表导出、订单凭证、合同生成等。本文将详细讲解如何基于 spring boot + flying saucer + thymeleaf 实现 pdf 生成,涵盖技术方案解析、核心注解与方法、项目搭建、关键问题解决、功能扩展等关键内容,新手也能快速上手。

本文核心亮点

  • 清晰层次结构:从技术解析到实操落地,逻辑循序渐进
  • 核心要点突出:重点讲解核心注解、方法及使用场景
  • 代码可复用:提供封装好的工具类,直接复制可用
  • 问题导向:针对性解决中文乱码、样式兼容等核心痛点

一、技术方案概述

1.1 核心技术栈

本方案基于三个核心技术组件协同实现 pdf 生成,各组件职责清晰、分工明确,共同完成从数据到 pdf 文档的转换流程:

组件作用核心优势
flying saucer(xhtmlrenderer)将 html/css 渲染为 pdf支持 css 2.1 标准,所见即所得,轻量高效
openpdfflying 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的资料请关注代码网其它相关文章!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2026  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com