文章目录
- 先准备:核心依赖(Maven)
- [一、不需要模板:直接创建 Word 文档](#一、不需要模板:直接创建 Word 文档)
-
- [完整示例:生成含文本、图片、表格的 Word](#完整示例:生成含文本、图片、表格的 Word)
- 关键说明:
- [二、利用模板:生成 Word 文档(核心场景)](#二、利用模板:生成 Word 文档(核心场景))
-
- [2.0 先学:模板制作规则](#2.0 先学:模板制作规则)
- [2.1 重点:往模板里塞各种数据(全面示例)](#2.1 重点:往模板里塞各种数据(全面示例))
- 三、核心总结
-
- [1. **无模板创建**:适合简单文档,用工厂类直接组装内容,代码直观;](#1. 无模板创建:适合简单文档,用工厂类直接组装内容,代码直观;)
- [2. **模板填充**:适合复杂/复用场景,模板负责样式,代码负责数据,分离清晰;](#2. 模板填充:适合复杂/复用场景,模板负责样式,代码负责数据,分离清晰;)
- [3. **关键标签总结**:](#3. 关键标签总结:)
系列文章:
Word 文件读取与导出(Apache POI + Poi-tl)
poi-tl(POI Template Language)是基于 Apache POI 的 Java Word 模板引擎,核心优势是低代码、跨平台、API友好,不用懂复杂的 POI 底层逻辑,就能快速生成 Word 文档。本文避开官网复杂的理论,聚焦「实用场景」,分两大部分带你上手,所有代码可直接复制运行!
先准备:核心依赖(Maven)
首先在项目中引入依赖,推荐使用最新稳定版 1.12.2(需 JDK1.8+、Apache POI5.2.2+):
xml
<!-- poi-tl 核心依赖 -->
<dependency>
<groupId>com.deepoove</groupId>
<artifactId>poi-tl</artifactId>
<version>1.12.2</version>
</dependency>
<!-- 依赖 Apache POI(无需单独引入 poi,poi-ooxml 已包含) -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.2.2</version>
</dependency>
一、不需要模板:直接创建 Word 文档
适合简单场景(比如动态生成报告、简历),无需提前制作模板,完全通过代码构建文档内容(文本、图片、表格等)。
核心 API:XWPFTemplate.create() + 工厂类(Documents 构建文档、Paragraphs 构建段落、Texts 构建文本、Tables 构建表格、Pictures 构建图片)。
完整示例:生成含文本、图片、表格的 Word
java
import com.deepoove.poi.XWPFTemplate;
import com.deepoove.poi.data.*;
import com.deepoove.poi.data.style.Style;
import java.io.FileOutputStream;
import java.io.IOException;
public class CreateWordWithoutTemplate {
public static void main(String[] args) throws IOException {
// 1. 构建文档样式(全局字体:微软雅黑,字号14)
Style globalStyle = Style.builder()
.buildFontFamily("微软雅黑")
.buildFontSize(14f)
.build();
// 2. 构建文档内容(段落+图片+表格)
Documents.DocumentBuilder docBuilder = Documents.of()
// 段落1:标题(居中、粗体、蓝色)
.addParagraph(Paragraphs.of()
.addText(Texts.of("个人技术总结报告")
.color("4472C4") // 蓝色
.bold(true)
.fontSize(18)
.create())
.center() // 居中对齐
.create())
// 段落2:普通文本(换行、斜体)
.addParagraph(Paragraphs.of("报告说明:本报告为 poi-tl 无模板生成示例\n")
.addText(Texts.of("生成时间:2026-02-05").italic(true).create())
.create())
// 段落3:插入本地图片(尺寸150*150)
.addParagraph(Paragraphs.of()
.addPicture(Pictures.ofLocal("D:/test/logo.png") // 替换为你的图片路径
.size(150, 150)
.create())
.center()
.create())
// 段落4:表格(2行3列,带样式)
.addParagraph(Paragraphs.of()
.addTable(Tables.of(
// 表头(背景色蓝色,文字白色)
Rows.of("技术栈", "熟练度", "备注").bgColor("4472C4").textColor("FFFFFF").center().create(),
// 内容行
Rows.of("Java", "精通", "5年经验").create()
).border(BorderStyle.DEFAULT).create())
.create());
// 3. 生成 Word 文档
try (XWPFTemplate template = XWPFTemplate.create(docBuilder.create(), globalStyle);
FileOutputStream out = new FileOutputStream("无模板生成的文档.docx")) {
template.write(out);
System.out.println("文档生成成功!");
}
}
}
关键说明:
- 无模板生成的核心是「通过工厂类组装内容」,支持链式调用,逻辑清晰;
- 支持设置全局样式(如字体、字号)和局部样式(如单个文本的颜色、粗体);
- 图片支持本地路径、网络URL、输入流(比如从数据库读取的图片字节流)。
- Poi-tl 的 Texts 类还提供了一系列文本样式设置方法,常用的有:
- bold(true) :表示给当前文本开启加粗样式
italic(true):设置文本斜体。
underline(UnderlinePatterns.SINGLE):设置文本下划线(单下划线、双下划线等)。
fontFamily("微软雅黑"):设置文本字体。
- bold(true) :表示给当前文本开启加粗样式
二、利用模板:生成 Word 文档(核心场景)
适合复杂场景(比如报表、合同、通知书),提前用 Word 制作模板(预留占位标签),再通过代码填充数据,实现「模板复用+数据动态替换」。
核心流程:制作模板 → 编写代码(编译模板+渲染数据+输出)
2.0 先学:模板制作规则
模板是普通的 .docx 文件(不支持 .doc),用 {``{变量名}} 作为占位标签,标签可放在任何位置(文本、表格、页眉、页脚、文本框):
- 普通变量:
{``{name}}、{``{age}} - 特殊类型变量(图片、表格等):
{``{@图片变量}}、{``{#表格变量}}、{``{*列表变量}}(后面详细讲) - 模板制作工具:Microsoft Word、WPS 均可,保存为
.docx格式即可。
2.1 重点:往模板里塞各种数据(全面示例)
下面覆盖 90% 实际场景,每个场景都包含「模板写法+代码示例+效果说明」。
场景1:填充普通文本(含样式、超链接)
模板写法(template.docx):
姓名:{{name}}
年龄:{{age}}
个人简介:{{intro}}
个人博客:{{@blogLink}}
代码示例:
java
import com.deepoove.poi.XWPFTemplate;
import com.deepoove.poi.data.Texts;
import com.deepoove.poi.data.HyperlinkTextRenderData;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
public class FillTextTemplate {
public static void main(String[] args) throws IOException {
// 1. 准备数据(普通文本、带样式文本、超链接文本)
Map<String, Object> data = new HashMap<>();
data.put("name", "张三"); // 普通文本
data.put("age", 28); // 数字自动转文本
// 带样式的文本(红色、粗体、字号14)
data.put("intro", Texts.of("5年Java开发,专注后端技术").color("FF0000").bold(true).fontSize(14).create());
// 超链接文本(点击跳转到博客)
data.put("blogLink", Texts.of("我的博客").link("https://blog.example.com").create());
// 2. 编译模板、渲染数据、输出文档
try (XWPFTemplate template = XWPFTemplate.compile("template.docx").render(data);
FileOutputStream out = new FileOutputStream("文本填充结果.docx")) {
template.write(out);
}
}
}
场景2:填充图片(本地、网络、流)
模板写法(template.docx):
个人头像:{{@avatar}}
网络图片:{{@netImage}}
代码示例:
java
import com.deepoove.poi.XWPFTemplate;
import com.deepoove.poi.data.Pictures;
import com.deepoove.poi.data.PictureType;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
public class FillImageTemplate {
public static void main(String[] args) throws IOException {
Map<String, Object> data = new HashMap<>();
// 1. 本地图片(指定尺寸200*200)
data.put("avatar", Pictures.ofLocal("D:/test/avatar.png")
.size(200, 200)
.altMeta("头像不存在时显示的文字") // 图片加载失败的备用文本
.create());
// 2. 网络图片(注意:需要网络连接)
data.put("netImage", Pictures.ofUrl("https://deepoove.com/images/icecream.png")
.size(150, 150)
.create());
// 3. 图片流(比如从数据库读取的图片字节流)
try (FileInputStream imgStream = new FileInputStream("D:/test/logo.jpeg")) {
data.put("streamImage", Pictures.ofStream(imgStream, PictureType.JPEG)
.size(100, 100)
.create());
}
// 生成文档
try (XWPFTemplate template = XWPFTemplate.compile("template.docx").render(data);
FileOutputStream out = new FileOutputStream("图片填充结果.docx")) {
template.write(out);
}
}
}
场景3:填充表格(含样式、合并单元格)
模板写法(template.docx):
成绩表:{{#scoreTable}}
代码示例:
java
import com.deepoove.poi.XWPFTemplate;
import com.deepoove.poi.data.*;
import com.deepoove.poi.data.style.BorderStyle;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
public class FillTableTemplate {
public static void main(String[] args) throws IOException {
Map<String, Object> data = new HashMap<>();
// 1. 构建表格(3行3列,带样式+合并单元格)
// 表头(背景色灰色,文字居中)
RowRenderData header = Rows.of("姓名", "科目", "成绩")
.bgColor("E6E6E6")
.center()
.bold(true)
.create();
// 内容行
RowRenderData row1 = Rows.of("张三", "数学", "95").create();
RowRenderData row2 = Rows.of("张三", "英语", "88").create();
// 2. 合并单元格(第0列的第1行和第2行合并)
MergeCellRule mergeRule = MergeCellRule.builder()
.map(Grid.of(1, 0), Grid.of(2, 0)) // 行1列0 → 行2列0
.build();
// 3. 组装表格(设置边框)
TableRenderData table = Tables.of(header, row1, row2)
.border(BorderStyle.DEFAULT) // 默认边框
.mergeRule(mergeRule) // 应用合并规则
.create();
data.put("scoreTable", table);
// 生成文档
try (XWPFTemplate template = XWPFTemplate.compile("template.docx").render(data);
FileOutputStream out = new FileOutputStream("表格填充结果.docx")) {
template.write(out);
}
}
}
场景4:填充列表(有序、无序)
模板写法(template.docx):
技术栈(有序):{{*techList}}
兴趣爱好(无序):{{*hobbyList}}
代码示例:
java
import com.deepoove.poi.XWPFTemplate;
import com.deepoove.poi.data.Numberings;
import com.deepoove.poi.data.NumberingFormat;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
public class FillListTemplate {
public static void main(String[] args) throws IOException {
Map<String, Object> data = new HashMap<>();
// 1. 有序列表(数字编号:1. 2. 3.)
data.put("techList", Numberings.of(NumberingFormat.DECIMAL)
.addItem("Java")
.addItem("SpringBoot")
.addItem("MySQL")
.create());
// 2. 无序列表(圆点符号)
data.put("hobbyList", Numberings.of(NumberingFormat.BULLET)
.addItem("篮球")
.addItem("阅读")
.addItem("旅行")
.create());
// 生成文档
try (XWPFTemplate template = XWPFTemplate.compile("template.docx").render(data);
FileOutputStream out = new FileOutputStream("列表填充结果.docx")) {
template.write(out);
}
}
}
场景5:条件判断(显示/隐藏内容)
用「区块对标签」{``{?条件变量}} 内容 {``{/条件变量}} 控制内容是否显示,支持布尔值、集合(空集合不显示)。
模板写法(template.docx):
{{?hasAward}}
获奖情况:
{{award}}
{{/hasAward}}
{{?hasProject}}
项目经历:
{{project}}
{{/hasProject}}
代码示例:
java
import com.deepoove.poi.XWPFTemplate;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
public class FillConditionTemplate {
public static void main(String[] args) throws IOException {
Map<String, Object> data = new HashMap<>();
data.put("hasAward", true); // 显示获奖情况
data.put("award", "2025年公司优秀员工");
data.put("hasProject", false); // 隐藏项目经历
data.put("project", "XX系统开发");
// 生成文档(仅显示获奖情况,项目经历不显示)
try (XWPFTemplate template = XWPFTemplate.compile("template.docx").render(data);
FileOutputStream out = new FileOutputStream("条件填充结果.docx")) {
template.write(out);
}
}
}
场景6:循环渲染(集合数据,比如多条订单)
用区块对标签 {``{?循环变量}} 循环内容 {``{/循环变量}} 遍历集合,支持内置变量(_index 索引、_is_first 是否第一个等)。
模板写法(template.docx):
订单列表:
{{?orders}}
订单号:{{orderNo}},金额:{{amount}}元,状态:{{status}}(索引:{{_index+1}})
{{/orders}}
代码示例:
java
import com.deepoove.poi.XWPFTemplate;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class FillLoopTemplate {
public static void main(String[] args) throws IOException {
Map<String, Object> data = new HashMap<>();
// 1. 准备集合数据(多条订单)
List<Map<String, Object>> orders = new ArrayList<>();
Map<String, Object> order1 = new HashMap<>();
order1.put("orderNo", "OD20260205001");
order1.put("amount", 999);
order1.put("status", "已支付");
Map<String, Object> order2 = new HashMap<>();
order2.put("orderNo", "OD20260205002");
order2.put("amount", 1999);
order2.put("status", "待发货");
orders.add(order1);
orders.add(order2);
data.put("orders", orders);
// 生成文档(循环渲染2条订单,索引从1开始)
try (XWPFTemplate template = XWPFTemplate.compile("template.docx").render(data);
FileOutputStream out = new FileOutputStream("循环填充结果.docx")) {
template.write(out);
}
}
}
场景7:填充图表(饼图、柱状图)
用「引用标签」(模板中图表的「可选文字」设置标签),支持饼图、柱状图、折线图等。
模板制作步骤:
- 在 Word 中插入图表(比如饼图);
- 右键图表 → 「图表区格式」→ 「可选文字」→ 标题处输入标签
{``{pieChart}}; - 保存模板为
template.docx。
代码示例:
java
import com.deepoove.poi.XWPFTemplate;
import com.deepoove.poi.data.ChartSingleSeriesRenderData;
import com.deepoove.poi.data.Charts;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
public class FillChartTemplate {
public static void main(String[] args) throws IOException {
Map<String, Object> data = new HashMap<>();
// 1. 构建饼图数据(单系列图表:各部门人数占比)
ChartSingleSeriesRenderData pieChart = Charts.ofSingleSeries("部门人数占比",
new String[]{"研发部", "产品部", "运营部"}) // 类别
.series("人数", new Integer[]{50, 10, 20}) // 数据
.create();
data.put("pieChart", pieChart);
// 生成文档(替换模板中的饼图数据,样式保留模板设置)
try (XWPFTemplate template = XWPFTemplate.compile("template.docx").render(data);
FileOutputStream out = new FileOutputStream("图表填充结果.docx")) {
template.write(out);
}
}
}
场景8:模板嵌套(主模板包含子模板)
用 {``{+嵌套变量}} 标签引入子模板,适合复用公共部分(比如页眉、页脚、通用条款)。
模板制作:
- 主模板(main.docx):
{``{+header}} 正文内容:{``{content}} - 子模板(header.docx):
公司内部文件 - 保密协议
代码示例:
java
import com.deepoove.poi.XWPFTemplate;
import com.deepoove.poi.data.Includes;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
public class FillNestedTemplate {
public static void main(String[] args) throws IOException {
Map<String, Object> data = new HashMap<>();
// 1. 引入子模板(本地子模板)
data.put("header", Includes.ofLocal("header.docx").create());
data.put("content", "本协议仅对公司内部员工有效,禁止外泄。");
// 生成文档(主模板会合并子模板内容)
try (XWPFTemplate template = XWPFTemplate.compile("main.docx").render(data);
FileOutputStream out = new FileOutputStream("嵌套模板结果.docx")) {
template.write(out);
}
}
}
三、核心总结
1. 无模板创建:适合简单文档,用工厂类直接组装内容,代码直观;
2. 模板填充:适合复杂/复用场景,模板负责样式,代码负责数据,分离清晰;
3. 关键标签总结:
涵盖官方所有核心标签、引用标签、控制标签、内置变量,按「标签类型+使用场景」分类,备注关键注意事项,方便直接查阅使用:
| 标签格式 | 标签类型 | 核心用途 | 示例 | 关键备注 |
|---|---|---|---|---|
{``{var}} |
普通文本标签 | 渲染文本(普通文本、带样式文本、超链接文本) | {``{name}}、{``{author.name}} |
1. 支持对象嵌套(用.分隔); 2. 数据模型可是 String、TextRenderData(带样式)、HyperlinkTextRenderData(超链接); 3. 标签可出现在文本、表格、页眉、页脚、文本框等任意位置 |
{``{@var}} |
图片标签 | 渲染图片(本地、网络、流、字节数组) | {``{@avatar}}、{``{@qrCode}} |
1. 数据模型需用 Pictures 工厂构建; 2. 支持设置尺寸、备用文本(图片加载失败时显示); 3. 支持 PNG/JPEG/SVG 等格式 |
{``{#var}} |
表格标签 | 渲染表格(含样式、合并单元格、动态行/列) | {``{#scoreTable}}、{``{#orderTable}} |
1. 数据模型需用 Tables/Rows/Cells 工厂构建; 2. 支持设置边框、背景色、行高、列宽; 3. 可通过 MergeCellRule 实现单元格合并 |
{``{*var}} |
列表标签 | 渲染有序/无序列表(支持多级列表) | {``{*techList}}、{``{*hobbyList}} |
1. 数据模型需用 Numberings 工厂构建; 2. 支持编号格式(数字、字母、罗马字符、圆点); 3. 多级列表需通过数据模型嵌套实现 |
{``{?var}}...{``{/var}} |
区块对标签 | 1. 条件判断(显示/隐藏内容); 2. 循环遍历(集合数据渲染) | 条件:{``{?hasAward}}获奖:{``{award}}{``{/hasAward}}; 循环:{``{?orders}}订单号:{``{orderNo}}{``{/orders}} |
1. 条件判断:var 为 null/false/空集合时隐藏内容; 2. 循环遍历:var 为集合时,按集合大小重复渲染区块内容; 3. 表格中使用时,开始/结束标签必须在同一单元格 |
{``{+var}} |
模板嵌套标签 | 引入子模板(复用公共部分,如页眉、通用条款) | {``{+header}}、{``{+footer}} |
1. 数据模型需用 Includes 工厂构建; 2. 支持本地子模板、网络子模板; 3. 子模板可单独渲染数据后合并到主模板 |
{``{var}}(写在「可选文字」中) |
引用图片标签 | 替换模板中已有图片(保留原图片尺寸/布局) | 模板中图片→右键「图表区格式」→「可选文字」→标题输入 {``{imgReplace}} |
1. 仅替换图片内容,不改变原模板图片的尺寸、位置; 2. 数据模型与普通图片标签一致(Pictures 构建) |
{``{var}}(写在图表「可选文字」中) |
引用多系列图表标签 | 替换模板中多系列图表数据(柱状图、折线图等) | 模板中图表→「可选文字」→输入 {``{barChart}} |
1. 数据模型为 ChartMultiSeriesRenderData(用 Charts 工厂构建); 2. 保留原图表样式,仅替换数据; 3. 支持 3D 条形图、柱形图、面积图、雷达图、散点图等 |
{``{var}}(写在图表「可选文字」中) |
引用单系列图表标签 | 替换模板中单系列图表数据(饼图、圆环图) | 模板中饼图→「可选文字」→输入 {``{pieChart}} |
1. 数据模型为 ChartSingleSeriesRenderData(用 Charts 工厂构建); 2. 支持 3D 饼图、圆环图; 3. 保留原图表样式,仅替换数据 |
{``{var}}(写在图表「可选文字」中) |
引用组合图表标签 | 替换模板中组合图表数据(柱形图+折线图等) | 模板中组合图表→「可选文字」→输入 {``{combChart}} |
1. 数据模型与多系列图表一致(ChartMultiSeriesRenderData); 2. 需指定每个系列的图表类型(BAR/LINE/AREA); 3. 保留原图表布局,仅替换数据 |
{``{EL表达式}} |
SpringEL 表达式标签 | 复杂逻辑计算(字符串处理、条件判断、日期格式化等) | {``{name.toUpperCase()}}、{``{age > 18 ? '成年' : '未成年'}}、{``{time.format('yyyy-MM-dd')}} |
1. 需通过 Configure 开启 useSpringEL(); 2. 支持类方法调用、运算符、三目运算、静态类使用; 3. 区块对结束标签可简化为 {``{/}} |
{``{_index}} |
循环内置变量标签 | 循环中获取当前迭代索引(从 0 开始) | {``{?orders}}{``{_index+1}}. {``{orderNo}}{``{/orders}} |
仅在区块对循环(集合数据)中可用 |
{``{_is_first}} |
循环内置变量标签 | 判断当前迭代是否为第一项 | {``{?orders}}{``{#if _is_first}}首单:{``{/if}}{``{orderNo}}{``{/orders}} |
布尔值(true/false),仅循环中可用 |
{``{_is_last}} |
循环内置变量标签 | 判断当前迭代是否为最后一项 | {``{?orders}}{``{orderNo}}{``{#if !_is_last}}、{``{/if}}{``{/orders}} |
仅循环中可用 |
{``{_has_next}} |
循环内置变量标签 | 判断当前迭代是否有下一项 | {``{?orders}}{``{orderNo}}{``{#if _has_next}};{``{/if}}{``{/orders}} |
仅循环中可用 |
{``{_is_even_item}} |
循环内置变量标签 | 判断当前迭代是否为「奇数项」(间隔 1) | {``{?orders}}{``{#if _is_even_item}}背景色:灰色{``{/if}}{``{orderNo}}{``{/orders}} |
仅循环中可用(索引从 0 开始,0 为第一项) |
{``{_is_odd_item}} |
循环内置变量标签 | 判断当前迭代是否为「偶数项」(间隔 1) | 同上 | 仅循环中可用 |
{``{#this}} |
循环内置变量标签 | 引用当前循环对象本身 | {``{?produces}}{``{#this}}{``{/produces}} |
文本标签中需用 {``{=#this}} 输出内容,避免与表格标签冲突 |
${var} 等自定义格式 |
自定义前后缀标签 | 替换默认标签前后缀(适配个人习惯) | 配置后用 ${name} 替代 {``{name}} |
需通过 Configure 配置 builder.buildGramer("${", "}"); 支持任意合法前后缀(如 {%var%}、<<var>>) |
补充说明(避免踩坑)
- 所有标签仅支持
.docx格式模板,不支持.doc旧格式; - 引用标签(图片/图表)的标签位置固定:需写在「可选文字」(新版本 Word 为「编辑替换文字」)中,而非直接写在文档正文;
- 区块对标签支持跨段落,但不支持跨表格单元格;
- 标签名称支持中文、字母、数字、下划线,可通过
buildGrammerRegex自定义标签格式规则; - 插件相关标签(如批注
{``{comment}}、附件{``{attachment}})需绑定对应插件,本质是「自定义标签+插件渲染」,核心标签格式仍遵循上述规则。
poi-tl 的核心是「模板即样式,数据即内容」,不用关心 POI 底层的段落、表格对象操作,专注业务逻辑即可。如果需要更复杂的功能(比如代码高亮、插入附件),可以参考官网插件部分,后续也可以继续分享!