杰瑞科技汇

Java HTML转PDF,如何实现?

Java HTML 转 PDF终极指南:5种主流方案对比与代码实战(2025最新版)

** 在现代Web应用开发中,将HTML内容转换为PDF是一个高频需求,无论是生成报表、合同、发票还是电子书,PDF因其格式固定、易于打印和分享的特性而备受青睐,本文作为2025年最新版Java HTML转PDF终极指南,将深入剖析5种主流实现方案,从纯Java库到云端API,手把手带你从零到一掌握核心技术,并提供可直接运行的代码示例,助你轻松应对项目中的各种转换挑战。

Java HTML转PDF,如何实现?-图1
(图片来源网络,侵删)

引言:为什么你的项目需要Java HTML转PDF功能?

想象一下以下场景:

  • 电商系统: 用户下单后,需要生成一份包含商品详情、价格、订单号的电子发票并发送到邮箱。
  • SaaS平台: 用户需要将配置好的个性化报告页面导出为PDF,以便离线查阅或打印。
  • 内容管理系统: 编辑发布了一篇图文并茂的文章,需要提供一键下载PDF版本的功能。

这些场景的共同点都是:将动态的、样式丰富的HTML页面,转化为一个静态的、跨平台一致的PDF文档。 使用Java作为后端语言来实现这一功能,是Java Web开发者的必备技能之一。

这个过程并非一帆风顺,如何完美保留HTML的CSS样式(特别是Flexbox、Grid等现代布局)?如何处理中文、特殊符号的字体问题?如何高效地处理大量并发转换请求?这些都是我们需要面对和解决的挑战。

本文将为你逐一拆解,并提供最实用的解决方案。

Java HTML转PDF,如何实现?-图2
(图片来源网络,侵删)

Flying Saucer (xhtmlrenderer) - Java原生方案的王者

定位: 纯Java实现,无外部依赖,适用于需要高度可控、服务器端渲染的场景。

核心原理: Flying Saucer(基于其核心项目xhtmlrenderer)是一个纯Java实现的HTML/CSS渲染引擎,它的工作原理是:将HTML文件作为输入,在内存中模拟一个浏览器环境,解析HTML和CSS,然后将其“绘制”到一个PDF文档的画布上。

优点:

  • 纯Java: 无需安装额外的系统软件(如浏览器或Headless Chrome),部署简单。
  • 成熟稳定: 项目历史悠久,被广泛使用,社区支持良好。
  • 高度可控: 所有逻辑都在JVM内部,便于集成和调试。

缺点:

Java HTML转PDF,如何实现?-图3
(图片来源网络,侵删)
  • CSS支持有限: 对现代CSS(如CSS3、Flexbox、Grid、部分CSS动画)的支持不完全,可能与浏览器渲染效果有差异。
  • 性能瓶颈: 对于复杂页面,渲染速度相对较慢,内存消耗较大。

实战代码示例:

pom.xml中添加依赖:

<dependency>
    <groupId>org.xhtmlrenderer</groupId>
    <artifactId>flying-saucer-pdf</artifactId>
    <version>9.1.22</version> <!-- 请使用最新版本 -->
</dependency>

核心转换代码:

import org.xhtmlrenderer.pdf.ITextRenderer;
import java.io.FileOutputStream;
import java.io.OutputStream;
public class FlyingSaucerExample {
    public static void main(String[] args) {
        String htmlContent = "<h1>Hello, Flying Saucer!</h1>" +
                             "<p>This is a paragraph generated from HTML to PDF.</p>" +
                             "<table border='1'><tr><th>Header 1</th><th>Header 2</th></tr>" +
                             "<tr><td>Data 1</td><td>Data 2</td></tr></table>";
        try (OutputStream os = new FileOutputStream("output_flying_saucer.pdf")) {
            ITextRenderer renderer = new ITextRenderer();
            // 如果HTML中引用了外部CSS或图片,需要设置基础路径
            // renderer.setDocumentFromString(htmlContent, "file:///path/to/your/resources/");
            renderer.setDocumentFromString(htmlContent);
            // 渲染并生成PDF
            renderer.layout();
            renderer.createPDF(os);
            System.out.println("PDF generated successfully using Flying Saucer!");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

使用技巧:

  • 对于中文,务必在HTML中指定字体,并在代码中加载字体文件,避免乱码。
  • 使用@page CSS规则来控制PDF的页面大小、边距等。

iText - 老牌PDF库的HTML支持

定位: 强大的PDF操作库,其HTML支持是作为其核心PDF生成功能的补充。

核心原理: iText 7引入了Html2Pdf模块,它内部集成了一个HTML解析器(基于jsoupcssparser),将HTML解析后,再利用iText强大的PDF布局引擎进行渲染。

优点:

  • PDF功能强大: iText本身就是业界顶级的PDF操作库,除了转HTML,还能进行PDF的创建、修改、加密等复杂操作。
  • 对iText用户友好: 如果项目已经在使用iText,那么无缝集成HTML转PDF功能非常方便。

缺点:

  • HTML/CSS支持同样有局限: 和Flying Saucer类似,它并非一个完整的浏览器内核,对复杂CSS的支持是“尽力而为”。
  • 商业许可: iText 7的AGPLv3许可对商业项目可能存在限制,需要购买商业许可才能避免法律风险。

实战代码示例:

添加pom.xml依赖:

<dependency>
    <groupId>com.itextpdf</groupId>
    <artifactId>html2pdf</artifactId>
    <version>4.0.3</version> <!-- 请使用最新版本 -->
</dependency>

核心转换代码:

import com.itextpdf.html2pdf.HtmlConverter;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
public class ITextExample {
    public static void main(String[] args) {
        String htmlContent = "<h1>Hello, iText 7!</h1>" +
                             "<p>This PDF is created with iText's HTML2PDF module.</p>";
        try {
            // 直接从字符串转换
            HtmlConverter.convertToPdf(htmlContent, new File("output_itext.pdf"));
            System.out.println("PDF generated successfully using iText!");
            // 也可以从HTML文件转换
            // HtmlConverter.convertToPdf(new FileInputStream("source.html"), new File("output_from_file.pdf"));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Apache PDFBox - Apache基金会的全能选手

定位: Apache开源的PDF工具库,功能全面,HTML转PDF是其功能之一。

核心原理: PDFBox的HTML转PDF功能也是通过解析HTML并映射到其内部的PDF对象模型来实现,它提供了一个相对简单的API。

优点:

  • 完全开源: 使用Apache License 2.0,商业友好,无任何后顾之忧。
  • 功能全面: 除了HTML转PDF,还支持PDF的创建、解析、提取文本、签名等。
  • 社区活跃: 作为Apache项目,拥有强大的社区支持。

缺点:

  • HTML支持较弱: 相较于前两者,PDFBox对HTML和CSS的支持更为基础,适合结构简单的HTML文档。
  • API可能不如iText直观: 对于某些复杂操作,API设计可能略显繁琐。

实战代码示例:

添加pom.xml依赖:

<dependency>
    <groupId>org.apache.pdfbox</groupId>
    <artifactId>pdfbox</artifactId>
    <version>2.0.27</version> <!-- 请使用最新版本 -->
</dependency>

核心转换代码:

import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.font.PDType1Font;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import java.io.IOException;
public class PDFBoxExample {
    public static void main(String[] args) {
        String htmlContent = "<h1>Hello, PDFBox!</h1><p>This is a simple example.</p>";
        try (PDDocument document = new PDDocument()) {
            PDPage page = new PDPage(PDRectangle.A4);
            document.addPage(page);
            // 注意:PDFBox本身不提供直接的HTML解析器,
            // 通常需要结合Jsoup等库来解析HTML,然后手动绘制到PDF上。
            // 这是一个简化版的思路,实际项目会更复杂。
            Document doc = Jsoup.parse(htmlContent);
            PDPageContentStream contentStream = new PDPageContentStream(document, page);
            contentStream.setFont(PDType1Font.HELVETICA_BOLD, 12);
            contentStream.beginText();
            contentStream.newLineAtOffset(50, 750);
            Element h1 = doc.selectFirst("h1");
            if (h1 != null) {
                contentStream.showText(h1.text());
            }
            contentStream.newLineAtOffset(0, -20);
            Element p = doc.selectFirst("p");
            if (p != null) {
                contentStream.setFont(PDType1Font.HELVETICA, 12);
                contentStream.showText(p.text());
            }
            contentStream.endText();
            contentStream.close();
            document.save("output_pdfbox.pdf");
            System.out.println("PDF generated successfully using PDFBox!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

重要提示: 上面的PDFBox示例仅作概念演示,实际HTML到PDF的转换逻辑非常复杂,对于生产环境,如果选择PDFBox,通常需要自己构建一个相对完善的HTML解析和布局引擎,或者寻找其社区中更高级的封装库。


Headless Chrome + Selenium - 效果最逼真的“浏览器方案”

定位: 追求100%还原页面显示效果,对复杂CSS和JavaScript有要求的场景。

核心原理: 这不是纯Java方案,而是“Java调用外部工具”的方案,我们通过Java代码(使用Selenium库)控制一个无头(Headless)的Chrome浏览器,让它加载指定的HTML URL或HTML字符串,然后将其当前渲染的页面截图或直接打印为PDF。

优点:

  • 效果完美: 因为是真实的浏览器内核,所以能完美支持所有现代CSS、JavaScript、Canvas、SVG等,生成的PDF与用户在浏览器中看到的一模一样。
  • 功能强大: 可以处理任何动态加载的内容,非常灵活。

缺点:

  • 依赖外部环境: 必须在服务器上安装Chrome浏览器及其驱动(如chromedriver),增加了部署的复杂度。
  • 资源消耗大: 每个转换实例都会启动一个浏览器进程,对服务器CPU和内存要求较高,不适合高并发场景。
  • 性能最慢: 启动浏览器、加载页面、渲染的过程耗时较长。

实战代码示例:

  1. 准备工作:

    • 服务器上安装Chrome浏览器。
    • 下载与Chrome版本匹配的chromedriver,并将其所在目录加入系统PATH。
  2. 添加pom.xml依赖:

    <dependency>
        <groupId>org.seleniumhq.selenium</groupId>
        <artifactId>selenium-java</artifactId>
        <version>4.9.1</version> <!-- 请使用最新版本 -->
    </dependency>
  3. 核心转换代码:

    import org.openqa.selenium.WebDriver;
    import org.openqa.selenium.chrome.ChromeDriver;
    import org.openqa.selenium.chrome.ChromeOptions;
    import java.io.File;
    import java.io.IOException;
    public class HeadlessChromeExample {
        public static void main(String[] args) {
            // 设置ChromeDriver路径 (如果未在PATH中)
            // System.setProperty("webdriver.chrome.driver", "/path/to/your/chromedriver");
            ChromeOptions options = new ChromeOptions();
            options.addArguments("--headless"); // 无头模式
            options.addArguments("--disable-gpu");
            options.addArguments("--no-sandbox");
            WebDriver driver = null;
            try {
                driver = new ChromeDriver(options);
                // 1. 从URL转换
                driver.get("https://www.example.com");
                // driver.quit(); // 注意:打印PDF后不能立即quit,否则PDF可能为空
                ((ChromeDriver) driver).getDevTools().createSession().getPrintPdf().print(new File("output_from_url.pdf"));
                System.out.println("PDF generated from URL using Headless Chrome!");
                // 2. 从HTML字符串转换 (需要将HTML保存为临时文件或提供data URL)
                String htmlContent = "<html><head><title>Test</title></head><body><h1>HTML from String</h1></body></html>";
                // 一个简单的方法是将其写入一个临时文件,然后通过file://协议加载
                File tempHtml = File.createTempFile("temp", ".html");
                java.nio.file.Files.write(tempHtml.toPath(), htmlContent.getBytes());
                driver.get("file://" + tempHtml.getAbsolutePath());
                ((ChromeDriver) driver).getDevTools().createSession().getPrintPdf().print(new File("output_from_string.pdf"));
                System.out.println("PDF generated from HTML String using Headless Chrome!");
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (driver != null) {
                    driver.quit();
                }
            }
        }
    }

云端API服务 - 开发者最省心的选择

定位: 追求极致开发效率、高可用性和弹性扩展的企业级应用。

核心原理: 将转换工作外包给专业的第三方服务,你的Java应用只需通过HTTP请求(REST API)将HTML内容发送给云端服务,服务处理完成后,将生成的PDF文件URL或二进制数据返回给你。

优点:

  • 高可用与高并发: 云服务通常有分布式架构,能轻松应对高并发请求,且SLA(服务等级协议)有保障。
  • 免运维: 无需关心服务器、浏览器驱动等任何底层环境,专注于业务逻辑。
  • 效果优异: 专业的云服务通常使用最新的浏览器引擎,渲染效果有保障。
  • 功能丰富: 除了转PDF,还可能提供加水印、添加页眉页脚、设置权限等增值服务。

缺点:

  • 成本: 对于高流量的应用,API调用会产生持续的费用。
  • 数据隐私: HTML数据需要传输到第三方服务器,可能涉及数据隐私和合规性问题。
  • 依赖网络: 服务质量受网络状况影响。

知名服务商:

  • Adobe PDF Services API: 行业标杆,功能强大,但价格较高。
  • Apitrary, DocRaptor, PDFShift: 专注于HTML转PDF,提供灵活的定价方案。

Java调用API的伪代码示例:

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
public class CloudApiExample {
    public static void main(String[] args) {
        String apiKey = "YOUR_API_KEY";
        String htmlContent = "<h1>Hello from Cloud API!</h1>";
        String apiUrl = "https://api.pdfshift.io/v2/convert"; // 以PDFshift为例
        try {
            URL url = new URL(apiUrl);
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setRequestMethod("POST");
            conn.setRequestProperty("Content-Type", "application/json");
            conn.setRequestProperty("Authorization", "Basic " + java.util.Base64.getEncoder().encodeToString(apiKey.getBytes()));
            conn.setDoOutput(true);
            String jsonInputString = "{\"source\": \"" + htmlContent.replace("\"", "\\\"") + "\", \"format\": \"pdf\"}";
            try (OutputStream os = conn.getOutputStream()) {
                byte[] input = jsonInputString.getBytes("utf-8");
                os.write(input, 0, input.length);
            }
            if (conn.getResponseCode() == 200) {
                BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
                StringBuilder response = new StringBuilder();
                String line;
                while ((line = br.readLine()) != null) {
                    response.append(line);
                }
                br.close();
                // 解析响应,获取PDF的URL或下载PDF
                System.out.println("Conversion successful! Response: " + response.toString());
            } else {
                System.out.println("Error: " + conn.getResponseCode());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

方案对比与选型建议

特性 Flying Saucer iText PDFBox Headless Chrome 云端API
CSS支持 一般 较好 较弱 完美 完美
JS支持 完美 完美
部署复杂度 极低 极低
性能 中等 中等 中等 快(服务端)
并发能力 中等 中等 中等
功能扩展性 一般 非常强
成本 免费 开源版免费,商业需授权 免费 需服务器/运维成本 按需付费
适用场景 简单报告、无复杂样式的文档 已使用iText的项目、对PDF操作要求高的场景 简单PDF生成、需要Apache生态支持 对视觉效果要求极高的场景、有动态内容 企业级应用、追求效率、高并发

如何选择?问自己三个问题:

  1. 我的HTML页面有多复杂?

    • 简单(表格、基本样式): 首选 Flying SaucerPDFBox
    • 复杂(Flexbox、Grid、JS渲染): 必须选择 Headless Chrome云端API
  2. 我的服务器环境和性能要求如何?

    • 资源有限、追求简单部署:Flying Saucer
    • 有资源、能处理复杂环境、追求最佳效果:Headless Chrome
    • 追求高可用、高并发、不想运维:云端API
  3. 我的项目预算有多少?

    • 零预算:Flying SaucerHeadless Chrome 之间做权衡。
    • 有预算,且追求效率和稳定性: 云端API 是最省心的选择。

最佳实践与常见陷阱

  1. 字体问题(中文乱码):

    • 问题: 服务器上没有安装HTML中使用的字体。
    • 解决:
      • 方案一(推荐): 在HTML中使用Web安全字体(如Arial, sans-serif)或通过@font-face嵌入字体文件(Base64编码或外部链接)。
      • 对于Java库,手动加载TTF字体文件,在Flying Saucer中:
        renderer.getFontResolver().addFont("path/to/your/simhei.ttf", "UTF-8", true);
  2. 资源路径问题(图片、CSS不显示):

    • 问题: Java库无法找到HTML中引用的本地图片或CSS文件。
    • 解决: 设置基础URL(Base URL),告诉渲染器,所有相对路径都相对于哪个目录。
      // Flying Saucer 示例
      renderer.setDocumentFromString(htmlContent, "file:///C:/projects/myapp/src/main/resources/static/");
  3. 分页与长文本处理:

    • 问题: 超长的HTML内容被渲染到一页PDF上,导致内容被截断或排版混乱。
    • 解决:
      • 使用page-break-after, page-break-before, page-break-inside等CSS属性来控制分页逻辑。
      • 在Java代码中,可以监听分页事件,在合适的位置插入新的PDPage对象(对于iText等库)。
  4. 性能优化:

    • 缓存: 对于不常变化的HTML,缓存生成的PDF文件,避免重复转换。
    • 异步处理: 对于耗时的转换任务(尤其是Headless Chrome),使用消息队列(如RabbitMQ, Kafka)进行异步处理,避免阻塞用户请求。
    • 连接池: 如果使用Headless Chrome,考虑使用Selenium Grid或浏览器管理工具(如Browserless.io)来管理浏览器实例,提高资源利用率。

Java HTML转PDF是一个看似简单实则蕴含诸多技术细节的课题,本文为你梳理了从经典到前沿的5种主流方案,并提供了详细的对比和选型指南。

  • 对于追求简单、快速、无依赖的中小型项目,Flying Saucer 是一个可靠的起点。
  • 如果你的项目已经深度使用iText,那么其内置的HTML转PDF功能值得考虑。
  • 当你遇到复杂CSS和JS,对视觉效果有“像素级”要求时,Headless Chrome 是不二之选,尽管它会带来一些运维成本。
  • 对于追求极致效率、高可用性和弹性扩展的企业级应用,将转换任务交给云端API服务,是让你从繁杂的运维工作中解放出来的最佳策略。

希望这篇详尽的指南能帮助你做出最适合自己项目的技术选型,并成功攻克Java HTML转PDF的挑战,选择最适合你的方案,开始你的高效开发之旅吧!

分享:
扫描分享到社交APP
上一篇
下一篇