超简单 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 底层的段落、表格对象操作,专注业务逻辑即可。如果需要更复杂的功能(比如代码高亮、插入附件),可以参考官网插件部分,后续也可以继续分享!

相关推荐
sensen_kiss2 小时前
Jupter Notebook 使用教程
大数据·人工智能·python·学习·数据分析
狂奔蜗牛飙车2 小时前
Python学习之路-Python3 迭代器与生成器学习详解
开发语言·python·学习·#python学习笔记·python迭代器生成器
云小逸2 小时前
【Nmap 源码学习】深度解析:main.cc 入口函数详解
网络·windows·学习·nmap
醇氧2 小时前
【Linux】centos 防火墙学习
linux·学习·centos
~光~~2 小时前
【嵌入式linux学习】06_中断子系统
linux·单片机·学习
蒸蒸yyyyzwd2 小时前
DDIA学习笔记
笔记·学习
LYS_06182 小时前
寒假学习(14)(HAL库5)
java·linux·学习
2501_901147832 小时前
学习笔记:基于摩尔投票法的高性能实现与工程实践
笔记·学习·算法·性能优化
神一样的老师2 小时前
【ELF2学习开发板】Linux 命令行读取 MPU6050 传感器数据(I2C 总线)实战
linux·运维·学习