超简单 poi-tl 学习博客:从0到1掌握Word生成(无需模板+模板填充)

文章目录

系列文章:
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("微软雅黑"):设置文本字体。

二、利用模板:生成 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:填充图表(饼图、柱状图)

用「引用标签」(模板中图表的「可选文字」设置标签),支持饼图、柱状图、折线图等。

模板制作步骤:
  1. 在 Word 中插入图表(比如饼图);
  2. 右键图表 → 「图表区格式」→ 「可选文字」→ 标题处输入标签 {``{pieChart}}
  3. 保存模板为 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>>

补充说明(避免踩坑)

  1. 所有标签仅支持 .docx 格式模板,不支持 .doc 旧格式;
  2. 引用标签(图片/图表)的标签位置固定:需写在「可选文字」(新版本 Word 为「编辑替换文字」)中,而非直接写在文档正文;
  3. 区块对标签支持跨段落,但不支持跨表格单元格;
  4. 标签名称支持中文、字母、数字、下划线,可通过 buildGrammerRegex 自定义标签格式规则;
  5. 插件相关标签(如批注 {``{comment}}、附件 {``{attachment}})需绑定对应插件,本质是「自定义标签+插件渲染」,核心标签格式仍遵循上述规则。

poi-tl 的核心是「模板即样式,数据即内容」,不用关心 POI 底层的段落、表格对象操作,专注业务逻辑即可。如果需要更复杂的功能(比如代码高亮、插入附件),可以参考官网插件部分,后续也可以继续分享!

相关推荐
西岸行者1 天前
学习笔记:SKILLS 能帮助更好的vibe coding
笔记·学习
悠哉悠哉愿意1 天前
【单片机学习笔记】串口、超声波、NE555的同时使用
笔记·单片机·学习
别催小唐敲代码1 天前
嵌入式学习路线
学习
毛小茛1 天前
计算机系统概论——校验码
学习
babe小鑫1 天前
大专经济信息管理专业学习数据分析的必要性
学习·数据挖掘·数据分析
winfreedoms1 天前
ROS2知识大白话
笔记·学习·ros2
在这habit之下1 天前
Linux Virtual Server(LVS)学习总结
linux·学习·lvs
我想我不够好。1 天前
2026.2.25监控学习
学习
im_AMBER1 天前
Leetcode 127 删除有序数组中的重复项 | 删除有序数组中的重复项 II
数据结构·学习·算法·leetcode
CodeJourney_J1 天前
从“Hello World“ 开始 C++
c语言·c++·学习