Java 高效生成 Word 文档:poi-tl 的使用

文章目录

在日常开发中,经常需要动态生成 Word 文档(如报表、合同、简历等),而 Apache POI 直接操作 Word 繁琐且易出错。poi-tl(POI Template Language)作为基于 Apache POI 的 Word 模板引擎,以「低代码、高灵活」的特性解决了这一痛点,支持文本、图片、表格、图表等多种元素的动态渲染,真正实现「模板 + 数据 = 输出」的极简开发模式。本文将从环境搭建到高级功能,结合实战 Demo 带你全面掌握 poi-tl 的使用。

一、poi-tl 核心优势

相比其他 Word 生成方案,poi-tl 具有明显优势:

  • 「跨平台」:基于 Java 开发,支持 Windows、Linux 等所有 Java 兼容环境

  • 「功能全面」:支持文本、图片、表格、列表、图表、循环、条件判断等几乎所有 Word 操作

  • 「易用性强」:低代码设计,只需准备模板和数据,无需关注底层 XML 结构

  • 「样式保留」:遵循「所见即所得」,模板中的格式样式会完全保留到输出文档

  • 「插件扩展」:支持自定义插件,可实现代码高亮、Markdown 渲染等高级功能

二、环境搭建

2.1 前置要求

  • JDK 1.8+

  • Apache POI 5.2.2+(poi-tl 已内置依赖,无需额外引入)

2.2 依赖配置

Maven 依赖

xml 复制代码
<dependency>
    <groupId>com.deepoove</groupId>
    <artifactId>poi-tl</artifactId>
    <version>1.12.2</version>
</dependency>

Gradle 依赖

groovy 复制代码
implementation 'com.deepoove:poi-tl:1.12.2'

三、快速入门(2分钟上手)

poi-tl 遵循 TDO 模式(Template + Data-model = Output),核心步骤仅3步:创建模板 → 准备数据 → 渲染输出。

3.1 步骤1:创建 Word 模板

新建 template.docx 文件,在需要动态替换的位置插入标签(默认标签格式 {``{变量名}}):

plain 复制代码
文档标题:{{title}}
作者:{{author}}
创建时间:{{createTime}}

3.2 步骤2:编写 Java 代码

java 复制代码
import com.deepoove.poi.XWPFTemplate;
import java.io.FileOutputStream;
import java.util.HashMap;
import java.util.Map;

public class QuickStartDemo {
    public static void main(String[] args) throws Exception {
        // 1. 准备数据(Map 结构,key 对应模板标签名)
        Map<String, Object> data = new HashMap<>();
        data.put("title", "poi-tl 快速入门教程");
        data.put("author", "技术学习者");
        data.put("createTime", "2026-03-25");

        // 2. 编译模板 + 渲染数据
        XWPFTemplate template = XWPFTemplate.compile("template.docx")
                .render(data);

        // 3. 输出文件
        template.writeAndClose(new FileOutputStream("output.docx"));
        System.out.println("Word 文档生成成功!");
    }
}

3.3 步骤3:渲染结果(纯文本展示)

生成的 output.docx 文档内容如下:

plain 复制代码
文档标题:poi-tl 快速入门教程
作者:技术学习者
创建时间:2026-03-25

四、核心功能实战

4.1 文本渲染(含样式、超链接)

文本标签支持普通文本、带样式文本、超链接、锚点等多种形式,推荐使用 Texts 工厂类构建。

模板内容

plain 复制代码
普通文本:{{name}}
带样式文本:{{styledText}}
超链接:{{link}}
锚点(跳转到附录1):{{anchor}}

代码实现

java 复制代码
import com.deepoove.poi.data.Texts;
import com.deepoove.poi.data.HyperlinkTextRenderData;

// 数据准备
Map<String, Object> data = new HashMap<>();
// 普通文本
data.put("name", "poi-tl");
// 带样式文本(绿色、粗体、14号字)
data.put("styledText", Texts.of("带样式的文本")
        .color("00FF00")
        .bold(true)
        .fontSize(14)
        .create());
// 超链接
data.put("link", Texts.of("访问官网")
        .link("https://deepoove.com/poi-tl/")
        .create());
// 锚点
data.put("anchor", Texts.of("跳转到附录")
        .anchor("appendix1")
        .create());

渲染结果(纯文本展示)

plain 复制代码
普通文本:poi-tl
带样式文本:带样式的文本(绿色、粗体、14号字)
超链接:访问官网(可点击跳转到 https://deepoove.com/poi-tl/)
锚点(跳转到附录1):跳转到附录(可点击跳转到文档中"附录1"锚点位置)

4.2 图片渲染

图片标签格式为 {``{@变量名}},支持本地图片、网络图片、图片流等多种来源,使用 Pictures 工厂类配置尺寸。

模板内容

plain 复制代码
本地图片:{{@localImg}}
网络图片:{{@urlImg}}
SVG图片:{{@svgImg}}

代码实现

java 复制代码
import com.deepoove.poi.data.Pictures;
import com.deepoove.poi.data.PictureType;
import java.io.FileInputStream;

// 数据准备
Map<String, Object> data = new HashMap<>();
// 本地图片(指定尺寸:宽150px,高100px)
data.put("localImg", Pictures.ofLocal("logo.png")
        .size(150, 100)
        .create());
// 网络图片
data.put("urlImg", Pictures.ofUrl("https://deepoove.com/images/icecream.png")
        .size(120, 120)
        .create());
// SVG图片
data.put("svgImg", Pictures.ofUrl("https://img.shields.io/badge/jdk-1.8%2B-orange.svg")
        .create());

渲染结果(纯文本描述)

plain 复制代码
本地图片:[显示本地 logo.png 图片,尺寸 150px × 100px,保持图片原有清晰度]
网络图片:[显示网络图片(https://deepoove.com/images/icecream.png),尺寸 120px × 120px]
SVG图片:[显示SVG格式图片(jdk 1.8+ 标识),自适应尺寸,清晰度无损耗]

4.3 表格渲染

表格标签格式为 {``{#变量名}},支持基础表格、带样式表格、单元格合并等功能,使用 TablesRowsCells 工厂类构建。

模板内容

plain 复制代码
基础表格:{{#basicTable}}
带样式表格:{{#styledTable}}
合并单元格表格:{{#mergeTable}}

代码实现

java 复制代码
import com.deepoove.poi.data.Tables;
import com.deepoove.poi.data.Rows;
import com.deepoove.poi.data.MergeCellRule;
import com.deepoove.poi.data.Grid;

// 数据准备
Map<String, Object> data = new HashMap<>();

// 1. 基础表格(2行2列)
data.put("basicTable", Tables.of(new String[][]{
        new String[]{"姓名", "年龄"},
        new String[]{"张三", "25"}
}).border(BorderStyle.DEFAULT).create());

// 2. 带样式表格(表头蓝色背景、白色文字)
data.put("styledTable", Tables.create(
        Rows.of("产品", "价格").textColor("FFFFFF")
                .bgColor("4472C4").center().create(),
        Rows.create("手机", "5999"),
        Rows.create("电脑", "8999")
));

// 3. 合并单元格表格(合并第2行第1-3列)
MergeCellRule mergeRule = MergeCellRule.builder()
        .map(Grid.of(1, 0), Grid.of(1, 2)) // 行1列0 到 行1列2 合并(索引从0开始)
        .build();
data.put("mergeTable", Tables.of(
        Rows.of("序号", "产品", "库存").center().bgColor("4472C4").create(),
        Rows.create("无数据", null, null)
).mergeRule(mergeRule).create());

渲染结果(纯文本展示)

1. 基础表格
plain 复制代码
---------------------
| 姓名 | 年龄       |
---------------------
| 张三 | 25         |
---------------------
(表格带默认边框,单元格内容左对齐)
2. 带样式表格
plain 复制代码
-----------------------------------
| 产品   | 价格      |
-----------------------------------
| 手机   | 5999      |
-----------------------------------
| 电脑   | 8999      |
-----------------------------------
(表头:蓝色背景、白色文字、居中对齐;表格带默认边框)
3. 合并单元格表格
plain 复制代码
-----------------------------------
| 序号 | 产品 | 库存 |
-----------------------------------
|     无数据      |
-----------------------------------
(表头:蓝色背景、居中对齐;第二行"无数据"跨3列合并,表格带默认边框)

4.4 循环与条件判断(区块对)

区块对标签格式为 {``{?变量名}}(开始)和 {``{/变量名}}(结束),支持循环渲染和条件显示,是动态生成列表、批量数据的核心功能。

模板内容

plain 复制代码
### 产品列表
{{?products}}
{{_index + 1}}. 产品名称:{{name}},价格:{{price}}元
{{/products}}

### 特殊说明
{{?hasSpecialNote}}
⚠️  本批次产品支持7天无理由退换
{{/hasSpecialNote}}

代码实现

java 复制代码
import java.util.ArrayList;
import java.util.List;

// 定义产品类
class Product {
    private String name;
    private double price;
    // 构造方法、getter/setter
    public Product(String name, double price) {
        this.name = name;
        this.price = price;
    }
    // getter/setter 省略(实际开发中需补充)
}

// 数据准备
Map<String, Object> data = new HashMap<>();

// 循环数据(产品列表)
List<Product> products = new ArrayList<>();
products.add(new Product("智能手机", 5999.0));
products.add(new Product("平板电脑", 3299.0));
products.add(new Product("无线耳机", 899.0));
data.put("products", products);

// 条件判断(是否显示特殊说明)
data.put("hasSpecialNote", true);

渲染结果(纯文本展示)

plain 复制代码
### 产品列表
1. 产品名称:智能手机,价格:5999.0元
2. 产品名称:平板电脑,价格:3299.0元
3. 产品名称:无线耳机,价格:899.0元

### 特殊说明
⚠️  本批次产品支持7天无理由退换

4.5 图表渲染

支持条形图、柱形图、饼图、折线图等多种图表,使用 Charts 工厂类构建,标签通过「可选文字」设置。

模板制作

  1. 在 Word 中插入图表(如饼图、柱形图)

  2. 右键图表 → 图表区格式 → 可选文字 → 输入标签名(如 {``{pieChart}}{``{barChart}}

代码实现

java 复制代码
import com.deepoove.poi.data.Charts;
import com.deepoove.poi.data.ChartSingleSeriesRenderData;
import com.deepoove.poi.data.ChartMultiSeriesRenderData;

// 数据准备
Map<String, Object> data = new HashMap<>();

// 饼图(单系列图表)
ChartSingleSeriesRenderData pieChart = Charts
        .ofSingleSeries("产品销量占比", new String[]{"手机", "平板", "耳机"})
        .series("销量", new Integer[]{5000, 3000, 4500})
        .create();
data.put("pieChart", pieChart);

// 柱形图(多系列图表)
ChartMultiSeriesRenderData barChart = Charts
        .ofMultiSeries("季度销量对比", new String[]{"Q1", "Q2", "Q3", "Q4"})
        .addSeries("手机", new Double[]{4500.0, 5200.0, 4800.0, 6000.0})
        .addSeries("平板", new Double[]{2800.0, 3100.0, 2900.0, 3500.0})
        .create();
data.put("barChart", barChart);

渲染结果(纯文本描述)

1. 饼图
plain 复制代码
图表标题:产品销量占比
图表类型:饼图
数据展示:
- 手机:5000(占比约 38.5%)
- 平板:3000(占比约 23.1%)
- 耳机:4500(占比约 38.4%)
(饼图各扇区对应不同颜色,鼠标悬浮可显示具体数值和占比)
2. 柱形图
plain 复制代码
图表标题:季度销量对比
图表类型:柱形图(分组柱形)
X轴:Q1、Q2、Q3、Q4(季度)
Y轴:销量
数据展示:
- 手机系列:Q1(4500)、Q2(5200)、Q3(4800)、Q4(6000)
- 平板系列:Q1(2800)、Q2(3100)、Q3(2900)、Q4(3500)
(不同产品系列用不同颜色柱形区分,清晰展示季度销量差异)

五、高级功能:插件使用

poi-tl 支持插件扩展,内置代码高亮、Markdown 渲染、批注、附件插入等实用插件,以下以代码高亮为例。

5.1 代码高亮插件

步骤1:引入依赖

xml 复制代码
<dependency>
    <groupId>com.deepoove</groupId>
    <artifactId>poi-tl-plugin-highlight</artifactId>
    <version>1.0.0</version>
</dependency>

步骤2:模板内容

plain 复制代码
### Java 代码示例
{{code}}

步骤3:代码实现

java 复制代码
import com.deepoove.poi.data.HighlightRenderData;
import com.deepoove.poi.plugin.highlight.HighlightRenderPolicy;
import com.deepoove.poi.plugin.highlight.HighlightStyle;
import com.deepoove.poi.XWPFTemplate;
import com.deepoove.poi.config.Configure;
import java.io.FileOutputStream;
import java.util.HashMap;
import java.util.Map;

public class HighlightDemo {
    public static void main(String[] args) throws Exception {
        // 数据准备
        Map<String, Object> data = new HashMap<>();

        // 代码高亮数据(Java语言,zenburn主题,显示行号)
        HighlightRenderData codeData = new HighlightRenderData();
        codeData.setCode("public class Demo {\n" +
                "    public static void main(String[] args) {\n" +
                "        System.out.println(\"Hello poi-tl!\");\n" +
                "    }\n" +
                "}");
        codeData.setLanguage("java");
        codeData.setStyle(HighlightStyle.builder()
                .withShowLine(true) // 显示行号
                .withTheme("zenburn") // 主题(可更换为monokai、github等)
                .build());
        data.put("code", codeData);

        // 配置插件(绑定标签和渲染策略)
        Configure config = Configure.builder()
                .bind("code", new HighlightRenderPolicy())
                .build();

        // 渲染模板
        XWPFTemplate template = XWPFTemplate.compile("template.docx", config)
                .render(data);
        template.writeAndClose(new FileOutputStream("output.docx"));
        System.out.println("带代码高亮的Word文档生成成功!");
    }
}

渲染结果(纯文本描述)

plain 复制代码
### Java 代码示例
1  public class Demo {
2      public static void main(String[] args) {
3          System.out.println("Hello poi-tl!");
4      }
5  }
(效果说明:代码按Java语法高亮,关键字(public、class、static等)显示对应主题颜色,注释、字符串颜色区分;左侧显示行号;背景为zenburn主题底色,代码可读性强,与IDE中代码显示效果一致)

六、常见配置与问题解决

6.1 标签格式自定义

默认标签格式为{``{变量名}},可自定义为 #{变量名}${变量名}

java 复制代码
// 自定义标签格式为 ${变量名}
Configure config = Configure.builder()
        .buildGramer("${", "}")
        .build();

6.2 Spring 表达式支持

引入 Spring EL 依赖后,可在标签中使用复杂表达式(如方法调用、三目运算):

xml 复制代码
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-expression</artifactId>
    <version>5.3.18</version>
</dependency>
java 复制代码
// 启用 Spring EL
Configure config = Configure.builder()
        .useSpringEL()
        .build();

模板中使用示例:

plain 复制代码
{{name.toUpperCase()}} <!-- 转大写 -->
{{price > 5000 ? '高端产品' : '中端产品'}} <!-- 三目运算 -->
{{new java.text.SimpleDateFormat('yyyy-MM-dd').format(createTime)}} <!-- 时间格式化 -->

6.3 常见问题

  1. NoSuchMethodError 异常:poi-tl 依赖 Apache POI 5.2.2+,若项目中存在低版本 POI,需升级或排除冲突依赖。

  2. 图片无法显示:检查图片路径是否正确,网络图片需确保网络通畅,建议使用本地图片测试;若图片格式不支持(如webp),需转换为jpg、png格式。

  3. 表格样式错乱:表格最大宽度为 A4 纸有效宽度(14.63cm),避免设置过宽的表格;复杂表格建议拆分或调整列宽。

  4. 图表渲染失败:确保模板中图表的「可选文字」与代码中数据的key一致;若使用Office 2007以下版本,需保存为docx格式。

七、Word 转 PDF

Maven 依赖

xml 复制代码
<!-- word 转 pdf -->
<dependency>
    <groupId>com.documents4j</groupId>
    <artifactId>documents4j-local</artifactId>
    <version>1.1.13</version>
</dependency>
<dependency>
    <groupId>com.documents4j</groupId>
    <artifactId>documents4j-transformer-msoffice-word</artifactId>
    <version>1.1.13</version>
</dependency>

工具方法

java 复制代码
    /**
     * 将 word 文件转成 pdf 文件
     * @param wordPath          word 文件路径
     * @param pdfPath           pdf 输出路径
     * @throws IOException
     */
    public static void wordConvertPdf(String wordPath, String pdfPath) throws IOException {
        InputStream wordInputStream = Files.newInputStream(Paths.get(wordPath));
        // 转成 pdf
        OutputStream outputStream = Files.newOutputStream(Paths.get(pdfPath));
        IConverter converter = LocalConverter.builder().build();
        converter.convert(wordInputStream).as(DocumentType.DOCX).to(outputStream).as(DocumentType.PDF).execute();
        converter.shutDown();
    }

八、总结

poi-tl 以「模板驱动」的设计大幅降低了 Java 生成 Word 文档的复杂度,从简单的文本替换到复杂的循环、图表、插件扩展,几乎能满足所有动态文档生成需求。核心要点:

  1. 遵循 TDO 模式,模板负责样式,数据负责内容,分离清晰,便于维护。

  2. 标签是核心,不同类型元素对应不同标签格式(文本{{}}、图片{{@}}、表格{{#}}、循环{{?}}),记准标签格式即可快速上手。

  3. 插件扩展了功能边界,可根据需求引入代码高亮、Markdown 等插件,灵活应对复杂场景。

通过本文的学习,你可以快速上手 poi-tl 并应用到实际项目中,如需更复杂的功能(如模板嵌套、动态表格、自定义渲染器),可参考 poi-tl 官方文档 进一步探索。

相关推荐
短剑重铸之日4 小时前
深入理解Sentinel: 01 一次服务雪崩问题排查经历
java·sentinel·降级熔断
马猴烧酒.4 小时前
【面试八股|操作系统】操作系统常见面试题详解笔记
java·linux·服务器·网络·数据结构·算法·eclipse
薛定谔的悦4 小时前
《储能系统中的故障定位》
java·服务器·前端
六义义4 小时前
SpringBoot 超详细全解(入门 + 实战 + 原理 + 面试)
java·spring boot·面试
fengxin_rou4 小时前
详解深浅拷贝:从原理到实现的完整指南
java·后端·浅拷贝·深拷贝
tsyjjOvO4 小时前
【SpringMVC 进阶】拦截器、文件上传、异常处理与 SSM 整合全解析
java·后端·spring
222you4 小时前
JUC读写锁和阻塞队列
java·开发语言·spring
AI英德西牛仔4 小时前
ChatGPT和Gemini导出word排版
人工智能·ai·chatgpt·word·deepseek·ds随心转