乐于分享
好东西不私藏

如何使用Java开发在线生成 pdf 文档 ?

如何使用Java开发在线生成 pdf 文档 ?

01

一、场景背景与技术选型

在后端业务开发过程中,研发人员经常会遇到这类需求:向用户提供标准化的电子凭证类文件,比如网银/支付宝/微信支付的电子发票、订单出库单、电子签章合同等。这类文件需要支持用户预览、打印、下载,而PDF格式是当前最适配这类需求的解决方案——它能保证跨设备、跨平台的格式一致性,不会因终端不同导致排版错乱。本文将围绕Java技术栈,详细讲解如何基于iText组件实现PDF文档的在线生成,从基础的文本写入到复杂的HTML转PDF场景,覆盖实际开发中最常用的核心玩法。

二、核心技术:iText组件介绍

iText是SourceForge开源社区的经典Java类库,专门用于PDF文档的生成与处理。它的核心能力包括:

  • • 直接生成PDF/RTF文档;
  • • 支持XML、HTML文件转换为PDF;
  • • 提供丰富的API控制PDF排版、字体、图片等元素。目前iText有两个主流版本:iText5iText7iText5是应用最广泛的版本,由大量社区开发者贡献代码,优点是资料多、易上手;iText7是官方对iText5的重构版本,架构更规范,但API变动较大。实际开发中,基础场景下两者的核心用法差异不大,本文以iText5为例展开讲解。

三、基础实现:环境准备与HelloWorld

3.1 引入Maven依赖

首先需要在项目的pom.xml中引入iText相关依赖,重点包含核心包、中文支持包、HTML转PDF辅助包等:

<dependencies>    <!-- iText核心包:PDF生成核心能力 -->    <dependency>        <groupId>com.itextpdf</groupId>        <artifactId>itextpdf</artifactId>        <version>5.5.11</version>    </dependency>    <!-- XML/HTML转PDF的辅助工具包 -->    <dependency>        <groupId>com.itextpdf.tool</groupId>        <artifactId>xmlworker</artifactId>        <version>5.5.11</version>    </dependency>    <!-- iText中文支持包:解决中文乱码问题 -->    <dependency>        <groupId>com.itextpdf</groupId>        <artifactId>itext-asian</artifactId>        <version>5.2.0</version>    </dependency>    <!-- HTML渲染辅助包:优化HTML转PDF的排版 -->    <dependency>        <groupId>org.xhtmlrenderer</groupId>        <artifactId>flying-saucer-pdf-itext5</artifactId>        <version>9.1.16</version>    </dependency>    <!-- HTML格式化工具包:修复不规范HTML标签 -->    <dependency>        <groupId>net.sf.jtidy</groupId>        <artifactId>jtidy</artifactId>        <version>r938</version>    </dependency></dependencies>

3.2 最简示例:生成HelloWorld PDF

下面是最基础的PDF生成代码,实现写入“hello world”并生成PDF文件,代码中添加详细注解便于理解:

import com.itextpdf.text.*;import com.itextpdf.text.pdf.BaseFont;import com.itextpdf.text.pdf.PdfWriter;import java.io.FileOutputStream;public class CreatePDFMainTest {    public static void main(String[] args) throws Exception {        // 1. 创建Document对象,指定页面大小为A4        Document document = new Document(PageSize.A4);        // 2. 绑定PDF写入器:将Document内容输出到指定文件(hello.pdf)        PdfWriter.getInstance(document, new FileOutputStream("hello.pdf"));        // 3. 加载中文字体:解决中文乱码问题        // STSong-Light:宋体;UniGB-UCS2-H:中文编码格式;NOT_EMBEDDED:不嵌入字体文件(减小PDF体积)        BaseFont bfchinese = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);        // 创建字体对象:指定字体、字号、样式(NORMAL为普通样式)        Font fontChinese = new Font(bfchinese, 12, Font.NORMAL);        // 4. 打开文档:开始写入内容前必须打开        document.open();        // 5. 创建段落对象:传入要写入的文本和字体        Paragraph paragraph = new Paragraph("hello world", fontChinese);        // 将段落添加到文档中        document.add(paragraph);        // 6. 关闭文档:释放资源,完成PDF生成        document.close();    }}

运行上述代码后,会在项目根目录生成“hello.pdf”文件,打开后可看到“hello world”文本正常显示(无中文乱码)。

四、进阶实战:HTML转PDF(适配复杂业务场景)

实际业务中,PDF的内容往往包含表格、图片、动态数据(比如入库单、发票),直接通过API写入文本/表格的方式开发效率低、维护成本高。更优的方案是:先编写HTML模板(包含动态变量),再将HTML转换为PDF——这种方式既能利用HTML的灵活排版,又能快速适配业务变更。

4.1 编写HTML模板(以入库单为例)

首先创建printDemo.html文件,模拟入库单的排版(包含表格、图片、固定文本):

<html><head>    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />    <title>入库单</title></head><body>    <div>        <!-- 入库单头部:标题、操作人、创建时间、二维码 -->        <div>            <table width="100%" border="0" cellspacing="0" cellpadding="0">                <tbody>                    <tr>                        <td height="40" colspan="2">                            <h3 style="font-weight: bold; text-align: center; letter-spacing: 5px; font-size: 24px;">入库单</h3>                        </td>                        <!-- Base64格式图片:避免图片路径依赖 -->                        <td width="12%" height="20" rowspan="2">                            <img style="width: 105px;height: 105px;" src="data:image/jpeg;base64,iVBORw0KGgoAAAANSUhEUgAAAH0AAAB9AQAAAACn+1GIAAAAqElEQVR42u3VMQ7DMAwDQP6A" />                        </td>                    </tr>                    <tr>                        <td width="50%" height="30">操作人:xxx</td>                        <td width="50%" height="30" colspan="2">创建时间:2021-09-14 12:00:00</td>                    </tr>                </tbody>            </table>        </div>        <!-- 入库单表格:商品列表 -->        <div style="margin-top: 5px; margin-bottom: 6px; margin-left: 4px"></div>        <div>            <table width="100%" style="border-collapse: collapse; border-spacing: 0;border:0px;">                <!-- 表格头部 -->                <tr style="height: 25px;">                    <td style="background: #eaeaea; text-align: center; border-left: 1px solid #000000; border-top: 1px solid #000000;" width="10%">序号</td>                    <td style="background: #eaeaea; text-align: center; border-left: 1px solid #000000; border-top: 1px solid #000000;" width="30%">商品</td>                    <td style="background: #eaeaea; text-align: center; border-left: 1px solid #000000; border-top: 1px solid #000000;" width="30%">单位</td>                    <td style="background: #eaeaea; text-align: center; border-left: 1px solid #000000; border-top: 1px solid #000000; border-right: 1px solid #000000;" width="30%">数量</td>                </tr>                <!-- 表格内容 -->                <tr>                    <td style="text-align: center; border-left: 1px solid #000000; border-top: 1px solid #000000;">1</td>                    <td style="text-align: center; border-left: 1px solid #000000; border-top: 1px solid #000000;">xxx沐浴露</td>                    <td style="text-align: center; border-left: 1px solid #000000; border-top: 1px solid #000000;"></td>                    <td style="text-align: center; border-left: 1px solid #000000; border-top: 1px solid #000000; border-right: 1px solid #000000;">3</td>                </tr>                <tr>                    <td style="text-align: center; border-left: 1px solid #000000; border-top: 1px solid #000000;">2</td>                    <td style="text-align: center; border-left: 1px solid #000000; border-top: 1px solid #000000;">xxx洗发水</td>                    <td style="text-align: center; border-left: 1px solid #000000; border-top: 1px solid #000000;"></td>                    <td style="text-align: center; border-left: 1px solid #000000; border-top: 1px solid #000000; border-right: 1px solid #000000;">4</td>                </tr>                <tr>                    <td style="text-align: center; border-left: 1px solid #000000; border-top: 1px solid #000000;">3</td>                    <td style="text-align: center; border-left: 1px solid #000000; border-top: 1px solid #000000;">xxx洗衣粉</td>                    <td style="text-align: center; border-left: 1px solid #000000; border-top: 1px solid #000000;"></td>                    <td style="text-align: center; border-left: 1px solid #000000; border-top: 1px solid #000000; border-right: 1px solid #000000;">5</td>                </tr>                <tr>                    <td style="text-align: center; border-left: 1px solid #000000; border-top: 1px solid #000000; border-bottom: 1px solid #000000;">4</td>                    <td style="text-align: center; border-left: 1px solid #000000; border-top: 1px solid #000000; border-bottom: 1px solid #000000;">xxx洗面奶</td>                    <td style="text-align: center; border-left: 1px solid #000000; border-top: 1px solid #000000; border-bottom: 1px solid #000000;"></td>                    <td style="text-align: center; border-left: 1px solid #000000; border-top: 1px solid #000000; border-right: 1px solid #000000; border-bottom: 1px solid #000000;">5</td>                </tr>            </table>        </div>    </div></body></html>

4.2 实现HTML转PDF的核心代码

编写Java代码读取HTML文件,并将其转换为PDF,代码添加详细注解:

import com.itextpdf.text.Document;import com.itextpdf.text.PageSize;import com.itextpdf.text.pdf.*;import com.itextpdf.tool.xml.*;import com.itextpdf.tool.xml.css.CssResolver;import com.itextpdf.tool.xml.css.StyleAttrCSSResolver;import com.itextpdf.tool.xml.html.Tags;import com.itextpdf.tool.xml.html.pdfelement.Image;import com.itextpdf.tool.xml.parser.XMLParser;import com.itextpdf.tool.xml.pipeline.css.CssResolverPipeline;import com.itextpdf.tool.xml.pipeline.html.HtmlPipeline;import com.itextpdf.tool.xml.pipeline.html.HtmlPipelineContext;import com.itextpdf.tool.xml.pipeline.html.AbstractImageProvider;import com.itextpdf.tool.xml.pipeline.html.CssAppliers;import com.itextpdf.tool.xml.pipeline.html.CssAppliersImpl;import com.itextpdf.tool.xml.pipeline.pdf.PdfWriterPipeline;import java.io.*;import java.nio.charset.StandardCharsets;import java.util.Base64;public class CreatePDFMainTest {    /**     * 将HTML字符串转换为PDF文件     * @param htmlStr 待转换的HTML字符串     * @throws Exception 转换过程中的异常(IO/排版异常等)     */    private static void writeToOutputStreamAsPDF(String htmlStr) throws Exception {        // 定义生成的PDF文件路径        String targetFile = "pdfDemo.pdf";        File targetFileObj = new File(targetFile);        // 如果文件已存在,先删除(避免覆盖失败)        if(targetFileObj.exists()) {            targetFileObj.delete();        }        // 1. 创建Document对象:指定页面大小A4,设置边距(左、右、上、下)        Document document = new Document(PageSize.A4, 25, 25, 15, 40);        // 2. 创建PdfWriter:绑定Document和输出文件流        PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(targetFile));        // 3. 设置PDF页眉页脚(此处为空页眉,可自定义PdfReportHeaderFooter类扩展)        PdfReportHeaderFooter header = new PdfReportHeaderFooter("", 8, PageSize.A4);        writer.setPageEvent(header);        // 设置PDF打印缩放为“无”(适配打印场景)        writer.addViewerPreference(PdfName.PRINTSCALING, PdfName.NONE);        // 4. 打开文档:开始转换前必须打开        document.open();        // 5. 初始化CSS解析器:处理HTML中的CSS样式        CssResolver cssResolver = new StyleAttrCSSResolver();        // 6. 初始化字体适配:解决HTML中文字体乱码        CssAppliers cssAppliers = new CssAppliersImpl(new XMLWorkerFontProvider(){            @Override            public com.itextpdf.text.Font getFont(String fontname, String encoding, boolean embedded, float size, int style, BaseColor color) {                try {                    // 加载宋体:保证中文正常显示                    BaseFont bfChinese = BaseFont.createFont("STSongStd-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);                    return new com.itextpdf.text.Font(bfChinese, size, style);                } catch (Exception e) {                    // 异常时使用默认字体                    return super.getFont(fontname, encoding, size, style);                }            }        });        // 7. 初始化HTML管道上下文:处理HTML标签和图片        HtmlPipelineContext htmlContext = new HtmlPipelineContext(cssAppliers);        htmlContext.setTagFactory(Tags.getHtmlTagProcessorFactory());        // 设置图片处理器:支持Base64图片和网络图片        htmlContext.setImageProvider(new AbstractImageProvider() {            @Override            public Image retrieve(String src) {                // 处理Base64格式图片                int pos = src.indexOf("base64,");                try {                    if (src.startsWith("data") && pos > 0) {                        // 解码Base64字符串为字节数组                        byte[] imgBytes = Base64.getDecoder().decode(src.substring(pos + 7));                        return Image.getInstance(imgBytes);                    } else if (src.startsWith("http")) {                        // 处理网络图片                        return Image.getInstance(src);                    }                } catch (Exception ex) {                    // 图片加载失败时返回null                    return null;                }                return null;            }            @Override            public String getImageRootPath() {                // 图片根路径:此处无需设置,返回null                return null;            }        });        // 8. 构建XMLWorker管道:CSS解析 -> HTML解析 -> PDF写入        PdfWriterPipeline pdfPipeline = new PdfWriterPipeline(document, writer);        HtmlPipeline htmlPipeline = new HtmlPipeline(htmlContext, pdfPipeline);        CssResolverPipeline cssPipeline = new CssResolverPipeline(cssResolver, htmlPipeline);        // 9. 初始化XMLWorker并执行转换        XMLWorker worker = new XMLWorker(cssPipeline, true);        XMLParser parser = new XMLParser(worker);        // 将HTML字符串转为字节流,指定UTF-8编码(避免中文乱码)        parser.parse(new ByteArrayInputStream(htmlStr.getBytes(StandardCharsets.UTF_8)));        // 10. 关闭文档:完成转换,释放资源        document.close();    }    /**     * 读取HTML文件内容为字符串     * @return HTML文件的字符串内容(读取失败返回null)     */    private static String readHtmlFile() {        StringBuffer textHtml = new StringBuffer();        try {            // 读取printDemo.html文件(需确保文件在项目根目录)            File file = new File("printDemo.html");            BufferedReader reader = new BufferedReader(new FileReader(file));            String tempString = null;            // 逐行读取文件内容并拼接            while ((tempString = reader.readLine()) != null) {                textHtml.append(tempString);            }            reader.close();        } catch (IOException e) {            // 捕获IO异常,返回null            e.printStackTrace();            return null;        }        return textHtml.toString();    }    public static void main(String[] args) throws Exception {        // 1. 读取HTML模板文件        String htmlStr = readHtmlFile();        if(htmlStr == null) {            System.out.println("HTML文件读取失败!");            return;        }        // 2. 将HTML转换为PDF        writeToOutputStreamAsPDF(htmlStr);        System.out.println("PDF生成成功!");    }    /**     * 自定义PDF页眉页脚类(空实现,可根据需求扩展)     */    static class PdfReportHeaderFooter extends PdfPageEventHelper {        private String headerText;        private int fontSize;        private PageSize pageSize;        public PdfReportHeaderFooter(String headerText, int fontSize, PageSize pageSize) {            this.headerText = headerText;            this.fontSize = fontSize;            this.pageSize = pageSize;        }        // 可重写onStartPage/onEndPage方法自定义页眉页脚        @Override        public void onEndPage(PdfWriter writer, Document document) {            super.onEndPage(writer, document);        }    }}

4.3 动态数据填充(模板变量替换)

上述HTML模板中的内容是固定的,实际业务中需要动态替换变量(比如操作人、商品列表、创建时间)。核心思路:在HTML模板中定义占位符(如{createTime}),读取HTML文件后,将占位符替换为实际业务数据。示例HTML模板(含占位符):

<html><head>    <meta charset="utf-8"></head><body>    <div>操作人:${operator}</div>    <div>创建时间:${createTime}</div>    <div>商品名称:${productName}</div></body></html>

替换逻辑示例(在readHtmlFile方法后添加):

// 模拟业务数据String operator = "张三";String createTime = "2025-04-15 10:00:00";String productName = "XX品牌沐浴露";// 替换HTML中的占位符htmlStr = htmlStr.replace("${operator}", operator)                 .replace("${createTime}", createTime)                 .replace("${productName}", productName);

也可以使用Freemarker、Velocity等模板引擎实现更复杂的动态渲染(比如循环渲染商品列表),核心逻辑一致:模板+数据=最终HTML,再转PDF。

五、总结

iText框架是Java开发中生成PDF的主流选择,轻量、易用,能满足大部分业务场景的需求:

  • • 简单文本/表格PDF:直接使用iText核心API快速实现;
  • • 复杂排版PDF:推荐HTML模板+转换的方式,兼顾开发效率和灵活性;
  • • 中文乱码问题:核心是加载中文字体(STSong-Light/UniGB-UCS2-H);
  • • 动态数据:通过占位符替换或模板引擎实现。对于超复杂的PDF场景(比如多页嵌套、复杂图表),可结合iText官方API文档扩展开发,或选择商业组件(如Aspose.PDF)。
推荐文章:

java面试100题讲解源文件

Spring Boot Starter 深度解析与实战系列

《MySQL系列教程》19篇