使用java操作Excel大家应该非常熟悉,但是操作word可能会稍微少一点。本文将提供一套基于 Apache POI 的完整解决方案,支持普通段落+表格单元格的占位符替换,逻辑简洁、可直接复用。
一、核心思路
- 模板设计:在 Word 文档中,用
{{占位符名}}标记需要替换的内容(如{{name}}、{{date}}); - 技术选型:使用 Apache POI(
poi-ooxml)解析.docx文档,遍历段落和表格,替换占位符; - 核心优势:无需依赖第三方付费组件,支持复杂文档结构,兼容主流 Word 版本。
二、实现步骤
1. 环境准备(添加 Maven 依赖)
核心依赖 poi-ooxml 用于处理 Office Open XML 格式(.docx),兼容 5.2.0 及以上版本(推荐使用最新稳定版):
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.2.0</version> <!-- 你可以使用最新版本 -->
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>5.2.0</version>
</dependency>2. 编写工具类(完整可运行代码)
工具类封装了「读取模板、替换占位符、保存文件」全流程,支持普通段落和表格单元格的替换,还保留了原文本格式(字体、大小、颜色):
package com.water.ocrimagerecognize.util;
import org.apache.poi.xwpf.usermodel.*;
import java.io.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class WordTemplateExporter {
public static void main(String[] args) {
// 输出文件路径
String outputPath = "output.docx";
try {
Map<String,String> contractData = new HashMap<>();
// 占位符替换
contractData.put("date", "2025");
contractData.put("s15", "FF");
contractData.put("s16", "增效降本");
contractData.put("name", "张三");
contractData.put("luca", "lucaju");
contractData.put("s1", "game");
// 加载资源文件夹resources/tem文件夹下的合同模板
//不在resources获取模板文件需要修改此处,通过这个方法只能获取resources中的资源文件
InputStream templateInputStream = new FileInputStream("/Users/lucaju/Documents/文档/水务/word/template.docx");
XWPFDocument document = new XWPFDocument(templateInputStream);
// 遍历文档中的段落
for (XWPFParagraph paragraph : document.getParagraphs()) {
if (paragraph == null || paragraph.getRuns().size() == 0){
continue;
}
//替换占位符
change(paragraph,contractData);
}
// 遍历文档中的表格
for (XWPFTable table : document.getTables()) {
for (XWPFTableRow row : table.getRows()) {
for (XWPFTableCell cell : row.getTableCells()) {
// 遍历单元格中的段落
for (XWPFParagraph cellParagraph : cell.getParagraphs()) {
//替换占位符
change(cellParagraph,contractData);
}
}
}
}
// 遍历文档中的图片
for (XWPFPictureData picture : document.getAllPictures()) {
// 图片不会被修改,直接跳过
//需要修改图片在此处
System.out.println("图片: " + picture.getFileName());
}
//该路径为相对路径,默认创建保存位置为项目文件所在盘符的根目录下
// 保存填充后的合同(将毫秒数添加到文件名防止命名冲突)
String filePath = "contract_" + System.currentTimeMillis() + ".docx";
//检查目录是否存在,不存在则创建
File directory = new File(filePath);
// 使用 FileOutputStream 保存填充后的合同
try (FileOutputStream out = new FileOutputStream(filePath)) {
document.write(out); // 将内容写入文件
}
System.out.println("输出完成");
} catch (Exception e) {
e.printStackTrace();
}
}
//替换占位符方法
public static void change(XWPFParagraph paragraph, Map<String, String> contractData){
for (XWPFRun run : paragraph.getRuns()) {
// 获取当前 run 的文本
String runText = run.getText(0);
if (runText == null){
continue;
}
StringBuilder newText = new StringBuilder(runText);
// 替换占位符
for (Map.Entry<String, String> entry : contractData.entrySet()) {
//此处的占位符“{{”和“}}”可以任意更改,合同模板文件随着替换即可。
String placeholder = "{{" + entry.getKey() + "}}";
int startIndex = newText.indexOf(placeholder);
while (startIndex != -1) {
newText.replace(startIndex, startIndex + placeholder.length(), entry.getValue());
startIndex = newText.indexOf(placeholder, startIndex + entry.getValue().length());
}
}
// 更新替换后的文本
run.setText(newText.toString(), 0);
}
}
/**
* 替换 Word 文件中的占位符(支持普通段落 + 表格单元格)
*
* @param document Word 文档对象
* @param placeholder 占位符,例如 {{name}}
* @param replacement 替换后的值,例如 "张三"
*/
private static void replacePlaceholder(XWPFDocument document, String placeholder, String replacement) {
// 1. 替换普通段落中的占位符(原有逻辑保留)
for (XWPFParagraph paragraph : document.getParagraphs()) {
replaceRunInParagraph(paragraph, placeholder, replacement);
}
// 2. 替换表格中的占位符(新增核心逻辑)
for (XWPFTable table : document.getTables()) { // 遍历所有表格
for (XWPFTableRow row : table.getRows()) { // 遍历表格的所有行
for (XWPFTableCell cell : row.getTableCells()) { // 遍历行的所有单元格
for (XWPFParagraph paragraph : cell.getParagraphs()) { // 遍历单元格内的所有段落
replaceRunInParagraph(paragraph, placeholder, replacement); // 替换段落中的占位符
}
}
}
}
}
/**
* 替换单个段落中所有 Run 里的占位符(抽取通用逻辑,避免重复代码)
*/
private static void replaceRunInParagraph(XWPFParagraph para, String placeholder, String replacement) {
List<XWPFRun> runs = para.getRuns();
int runCount = runs.size();
// 情况1:段落只有1个 Run(直接替换)
if (runCount == 1) {
XWPFRun run = runs.get(0);
String text = run.getText(0);
if (text != null && text.contains(placeholder)) {
run.setText(text.replace(placeholder, replacement), 0);
}
return;
}
// 情况2:段落有多个 Run(合并文本后替换)
StringBuilder mergedText = new StringBuilder();
// 第一步:合并所有 Run 的文本
for (XWPFRun run : runs) {
String text = run.getText(0);
if (text != null) {
mergedText.append(text);
}
}
// 检查合并后的文本是否包含占位符
String finalText = mergedText.toString();
if (!finalText.contains(placeholder)) {
return; // 不包含则无需处理
}
// 第二步:替换占位符
finalText = finalText.replace(placeholder, replacement);
// 第三步:清空原有所有 Run(兼容低版本 POI 的写法)
// 注意:要倒序删除,避免索引错乱
for (int i = runCount - 1; i >= 0; i--) {
para.removeRun(i);
}
// 第四步:创建新的 Run,写入替换后的文本
XWPFRun newRun = para.createRun();
newRun.setText(finalText, 0);
// (可选)复制原有文本的格式(如字体、大小、颜色)
if (runCount > 0) {
XWPFRun originalFirstRun = runs.get(0); // 取第一个 Run 的格式
newRun.setFontFamily(originalFirstRun.getFontFamily());
newRun.setFontSize(originalFirstRun.getFontSize());
newRun.setColor(originalFirstRun.getColor());
}
}
}3. 模板编辑规范
- 占位符格式:统一使用
{{占位符名}}(如{{name}}、{{date}}),可自定义分隔符(需同步修改代码中placeholder的拼接逻辑); 关键注意点:
- 占位符必须作为一个整体输入(直接复制粘贴
{{name}},或一次性输入完成); - 避免输入一半保存、再续输的操作(会导致占位符被拆分成多个
Run,替换失败); - 模板文件需保存为
.docx格式(不支持.doc旧格式,如需兼容可先转成.docx)。
- 占位符必须作为一个整体输入(直接复制粘贴
三、使用示例
编辑模板
template.docx,内容如下:姓名:{{name}} 日期:{{date}} 项目:{{s1}} 目标:{{s16}}- 运行
main方法,传入替换数据; 生成的文件中,占位符会被自动替换为对应值:
姓名:张三 日期:2025年11月 项目:game 目标:增效降本
这套方案轻量化、无额外依赖,适合合同生成、报表导出、通知书批量制作等场景,可直接集成到 Spring Boot、SSM 等主流 Java 项目中。
评论 (0)