1. 简介
在项目开发中,生成Word文档常用于自动化生成合同、报告、数据汇总等场景,可大幅提升效率并减少人工错误。通过模板引擎(如poi-tl)结合动态数据,能快速生成格式统一、内容灵活的文档,满足业务对标准化和个性化的双重需求。
本篇文章我们将介绍一个强劲的开源组件poi-tl,该组件能超级超级简单的根据模板生成word文档。
什么是POI-TL?
poi-tl 是一款 Word 模板引擎,可根据 Word 模板和数据生成新文档。Word 模板具有丰富的样式。poi-tl 会在生成的文档中完美保留模板中的样式。
poi-tl 是一款“无逻辑”模板引擎。它没有复杂的控制结构和变量赋值,只有标签。某些标签可替换为文本、图片、表格等,某些标签会隐藏部分文档内容,而其他标签则会对一系列文档内容进行循环。

2.实战案例
2.1 环境准备
引入依赖
<dependency>
<groupId>com.deepoove</groupId>
<artifactId>poi-tl</artifactId>
<version>1.12.2</version>
</dependency>
准备Word模板如下

准备数据模型
public class PersonalInfo {
// 学院
private String college;
// 专业
private String major;
// 姓名
private String name;
// 性别
private String gender;
// 民族
private String nation;
// 出生年月
private String birthDate;
// 籍贯
private String nativePlace;
// 现所在地
private String currentLocation;
// 学历
private String education;
// 政治面貌
private String politicalStatus;
// 婚姻状况
private String maritalStatus;
// 就业意向
private String employmentIntention;
// 兴趣爱好
private String hobbies;
// 自我评价
private String selfEvaluation;
// 住址
private String address;
// 联系电话
private String phoneNumber;
// 电子邮箱
private String email;
// 头像
private String img ;
}
2.2 根据模板生成文档
PersonalInfo person = new PersonalInfo(
"计算机学院",
"软件工程",
"张三",
"男",
"汉族",
"1995年08月",
"湖南长沙",
"北京",
"本科",
"共青团员",
"未婚",
"希望从事软件开发相关工作",
"编程、阅读、篮球",
"本人学习能力强,有良好的团队合作精神和沟通能力",
"北京市海淀区XX街道XX号",
"13812345678",
"zhangsan@example.com",
"https://img1.baidu.com/it/u=2709364071,2311129161&fm=253&app=138&f=JPEG?w=800&h=800") ;
ClassPathResource resource = new ClassPathResource("templates/person_template.docx") ;
XWPFTemplate.compile(resource.getInputStream())
.render(person)
.writeToFile("e:/person.docx") ;
注意,我们上面的模板中对于图片使用的占位符使用了 '@'。
最终生成的文档如下:

2.3 生成表格数据
表格标签以#开头,例如{{#table}},它将被渲染为一个Word表格,具有N行N列。N的值取决于表格标签的数据。如下示例:
模板定义

代码实现
Map<String, Object> dataModal = new HashMap<>() ;
dataModal.put("hobbies", Tables.of(new String[][] {
new String[] {"烹饪", "绘画", "电影", "徒步", "编程"}
}).border(BorderStyle.DEFAULT).create());
XWPFTemplate.compile(resource.getInputStream())
.render(dataModal)
.writeToFile("e:/person.docx") ;
生成文档

2.4 生成列表
列表标签对应于Word的符号列表或编号列表,以*开头,例如{{*list}}。
模板定义

代码实现
ClassPathResource resource = new ClassPathResource("templates/person_template.docx") ;
Map<String, Object> dataModal = new HashMap<>() ;
dataModal.put("webflux_advantage", Numberings.create(
"非阻塞异步处理,提升并发能力", "支持背压(Backpressure)机制",
"活的运行环境:支持 Netty 和 Servlet 容器", " 天然适合实时和流式应用")) ;
XWPFTemplate.compile(resource.getInputStream())
.render(dataModal)
.writeToFile("e:/person.docx") ;
生成文档

2.4 区块
一个区块(section)由前后两个标签组成,起始标签以 ? 标识,结束标签以 / 标识,例如,{{?section}} 是 sections 区块的起始标签,{{/section}} 是结束标签,其中 section 是这个区块的名称。
在处理一系列文档元素时,区块超级有用。位于区块内的文档元素(文本、图片、表格等)可以根据区块的取值被渲染零次、一次或 N 次。
- 假值或空集合
如果区块的值为 null、false 或空集合,则位于该区块内的所有文档元素都将不会被显示,这与 if 语句条件为假的情况类似。
模板定义

代码实现
ClassPathResource resource = new ClassPathResource("templates/person_template.docx") ;
Map<String, Object> dataModal = new HashMap<>() ;
dataModal.put("isVIP", false) ;
dataModal.put("hasDiscount", true) ;
dataModal.put("discountAmount", 666) ;
XWPFTemplate.compile(resource.getInputStream())
.render(dataModal)
.writeToFile("e:/person.docx") ;
生成文档

- 非空集合
如果区块的值为非空集合,则该区块内的文档元素将根据集合的大小循环一次或 N 次,这与 foreach 语法类似。
模板定义

代码实现
// 准备数据模型
public record Tech(Long id, String tech, String version) {}
dataModal.put("techStack", List.of(
new Tech(1L, "Spring Boot", "4.0.0"),
new Tech(2L, "Spring Cloud", "2025.0.1"),
new Tech(3L, "Spring MVC", "3.0.1"))) ;
XWPFTemplate.compile(resource.getInputStream())
.render(dataModal)
.writeToFile("e:/person.docx") ;
生成文档

2.5 生成图表
通过在模板中添加各种图表后,进行简单的标签文本设置可以动态的生成各种图表(Word中支持的)。
模板定义

选择了你想要的图表后来,接下来最重大的一步设置标签:

代码实现
ChartMultiSeriesRenderData chart = Charts
.ofMultiSeries("ChartTitle", new String[] { "中文", "English" })
.addSeries("地区", new Double[] { 15.0, 6.0 })
.addSeries("语言", new Double[] { 223.0, 119.0 })
.create();
dataModal.put("barChart", chart);
XWPFTemplate.compile(resource.getInputStream())
.render(dataModal)
.writeToFile("e:/person.docx") ;
生成文档

2.6 Spring表达式支持
SpEL表达式,支持在运行时查询和操作对象图,可作为独立组件使用,接下来,我们基于Spring Boot环境使用。
模板定义

代码实现
dataModal.put("name", "pack_xg") ;
dataModal.put("empty", false) ;
dataModal.put("sex", true) ;
dataModal.put("time", new Date()) ;
dataModal.put("price", 548378389) ;
dataModal.put("dogs", new Dog[] {new Dog("汪汪", 2), new Dog("狗蛋", 10)}) ;
dataModal.put("localDate", LocalDate.now()) ;
ConfigureBuilder builder = Configure.builder();
builder.useSpringEL() ;
XWPFTemplate.compile(resource.getInputStream(), builder.build())
.render(dataModal)
.writeToFile("e:/person.docx") ;
代码中,我们通过builder#useSpringEL方法开启SpEL表达式的支持。
生成文档

2.7 生成文档并下载
接下来,我们通过Controller接口生成文档并提供下载功能。
private final HttpServletResponse response ;
@GetMapping("/download")
public void download() throws Exception {
PersonalInfo person = new PersonalInfo(
"计算机学院",
"软件工程",
"张三",
"男",
"汉族",
"1995年08月",
"湖南长沙",
"北京",
"本科",
"共青团员",
"未婚",
"希望从事软件开发相关工作",
"编程、阅读、篮球",
"本人学习能力强,有良好的团队合作精神和沟通能力",
"北京市海淀区XX街道XX号",
"13812345678",
"zhangsan@example.com",
"https://img1.baidu.com/it/u=2709364071,2311129161&fm=253&app=138&f=JPEG?w=800&h=800") ;
ClassPathResource resource = new ClassPathResource("templates/person_template.docx") ;
Map<String, Object> dataModal = new HashMap<>() ;
dataModal.put("person", person) ;
dataModal.put("hobbies", Tables.of(new String[][] {
new String[] {"烹饪", "绘画", "电影", "徒步", "编程"}
}).border(BorderStyle.DEFAULT).create());
dataModal.put("webflux_advantage", Numberings.create(
"非阻塞异步处理,提升并发能力", "支持背压(Backpressure)机制",
"活的运行环境:支持 Netty 和 Servlet 容器", " 天然适合实时和流式应用")) ;
dataModal.put("isVIP", false) ;
dataModal.put("hasDiscount", true) ;
dataModal.put("discountAmount", 666) ;
dataModal.put("techStack", List.of(
new Tech(1L, "Spring Boot", "4.0.0"),
new Tech(2L, "Spring Cloud", "2025.0.1"),
new Tech(3L, "Spring MVC", "3.0.1"))) ;
ChartMultiSeriesRenderData chart = Charts
.ofMultiSeries("ChartTitle", new String[] { "中文", "English" })
.addSeries("地区", new Double[] { 15.0, 6.0 })
.addSeries("语言", new Double[] { 223.0, 119.0 })
.create();
dataModal.put("barChart", chart);
response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
response.setCharacterEncoding("utf-8");
String fileName = URLEncoder.encode("个人简历.docx", "UTF-8").replaceAll("\+", "%20");
response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename*=utf-8''" + fileName);
XWPFTemplate.compile(resource.getInputStream())
.render(dataModal)
.write(response.getOutputStream()) ;
response.flushBuffer() ;
}

深度好文
好东西
好好学习天天向上
这个厉害了👏
收藏了,感谢分享
图片吗