跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
Javajava

Java 填充 Word 模板工具类实现

综述由AI生成介绍在 Java 项目中利用 POI-TL 和 Aspose.Words 库填充 Word 模板的方法。涵盖普通文本字段、列表表格及复选框的处理逻辑。通过反射将实体类数据转换为 Map,结合 MailMerge 功能实现动态文档生成。代码包含工具类封装及测试示例,解决了域代码匹配错误等常见问题,适用于简历、合同等场景的自动化文档生成。

二进制发布于 2026/3/27更新于 2026/5/2931 浏览

前言

最近有个 Java 填充 Word 模板的需求,包括文本、列表和复选框勾选,写一个工具类,以此记录。

一、设置 word 模板

选择文档中要填充的地方点击 -> 选择插入 -> 文档部件 -> 域 -> 域名 (mergeField) -> 填写变量名称。

普通字段

[图片]

填充完毕: [图片]

列表字段

操作和普通字段一样,区别是需要在首行第一列插入列表开始域,首行最后一列插入结束域,中间正常字段。格式为:StartTable:<数组字段名>,EndTable:<数组字段名> [图片]

复选框

复选框找了好多种方法尝试没有成功,最后取巧,和普通字段一样设置占位符,通过代码逻辑处理。 [图片]

二、代码

1. 引入依赖

poi/hutool/aspose-words/gson:

<!-- hutool 工具类 -->
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.7.14</version>
</dependency>
<!-- word 模板数据解析 -->
<dependency>
    <groupId>com.deepoove</groupId>
    <artifactId>poi-tl</artifactId>
    <version>1.9.0-beta</version>
</dependency>
<!-- word/pdf 操作 -->
<dependency>
    <groupId>com.aspose</groupId>
    <artifactId>aspose-words</artifactId>
    <version>18.8</version>
</dependency>
<dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
    <version>2.8.9</version>
</dependency>

2. 模板放入项目

这里是放在项目里,也可以放在云上存储。 [图片]

License 文件放入 resources,否则会有水印。Aspose.Words 在 Maven 仓库中如果没有 License 包,需要下载后安装在本地仓库。

安装命令

mvn install:install-file -Dfile=路径/aspose-words-18.8.jar -DgroupId=com.aspose -DartifactId=aspose-words -Dversion=18.8 -Dpackaging=jar

引入 License 配置 注意:Aspose.Words 需要有效 License 文件,此处省略具体配置内容,请确保拥有合法授权。

3. 代码

实体类
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.Arrays;
import java.util.List;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class FillWordDTO implements Serializable {
    private String name;
    private String age;
    private String yuyan;
    private String yingyu;
    private String deyu;
    private String fayu;
    private String zhengshu;
    private String yiji;
    private String erji;
    private List<ExperienceList> experienceList;

    public static FillWordDTO create() {
        FillWordDTO fillWordDTO = new FillWordDTO();
        fillWordDTO.setName("小王");
        fillWordDTO.setAge("18");
        fillWordDTO.setYuyan("☑");
        fillWordDTO.setYingyu("☑");
        fillWordDTO.setDeyu("□");
        fillWordDTO.setFayu("☑");
        fillWordDTO.setZhengshu("☑");
        fillWordDTO.setYiji("☑");
        fillWordDTO.setErji("□");
        fillWordDTO.setExperienceList(Arrays.asList(
            new ExperienceList("小王", "2020-01-01", "2020-01-01", "小王"),
            new ExperienceList("小王", "2020-01-01", "2020-01-01", "小王")
        ));
        return fillWordDTO;
    }
}

@Data
@AllArgsConstructor
@NoArgsConstructor
class ExperienceList {
    private String school;
    private String startTime;
    private String endTime;
    private String remark;
}
工具类
import com.aspose.words.*;
import com.aspose.words.DataRow;
import com.aspose.words.DataTable;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.beans.PropertyDescriptor;
import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class ContractUtil {
    private ContractUtil() {}

    /**
     * 调整 bufferedimage 大小
     * @param source BufferedImage 原始 image
     * @param targetW int 目标宽
     * @param targetH int 目标高
     * @param flag boolean 是否同比例调整
     * @return BufferedImage 返回新 image
     */
    public static BufferedImage resizeBufferedImage(BufferedImage source, int targetW, int targetH, boolean flag) {
        int type = source.getType();
        BufferedImage target = null;
        double sx = (double) targetW / source.getWidth();
        double sy = (double) targetH / source.getHeight();
        if (flag && sx > sy) {
            sx = sy;
            targetW = (int) (sx * source.getWidth());
        } else if (flag && sx <= sy) {
            sy = sx;
            targetH = (int) (sy * source.getHeight());
        }
        if (type == BufferedImage.TYPE_CUSTOM) {
            ColorModel cm = source.getColorModel();
            WritableRaster raster = cm.createCompatibleWritableRaster(targetW, targetH);
            boolean alphaPremultiplied = cm.isAlphaPremultiplied();
            target = new BufferedImage(cm, raster, alphaPremultiplied, null);
        } else {
            target = new BufferedImage(targetW, targetH, type);
        }
        Graphics2D g = target.createGraphics();
        g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
        g.drawRenderedImage(source, AffineTransform.getScaleInstance(sx, sy));
        g.dispose();
        return target;
    }

    /**
     * 填充 word 模板(object 数据格式)
     *
     * @param modelWordByte word 模版二进制文件
     * @param obj 要填充的数据
     * @return 组合数据之后的 word 二进制
     */
    public static byte[] fillWordDataByDomain(byte[] modelWordByte, Object obj) {
        try {
            Class<?> aClass = obj.getClass();
            Field[] fields = aClass.getDeclaredFields();
            Map<String, Object> data = new HashMap<>(fields.length);
            for (Field field : fields) {
                PropertyDescriptor pd = new PropertyDescriptor(field.getName(), aClass);
                Method method = pd.getReadMethod();
                String key = field.getName();
                Object value = method.invoke(obj);
                if (value != null) {
                    data.put(key, value);
                }
            }
            return fillWordDataByMap(modelWordByte, data);
        } catch (Exception e) {
            e.printStackTrace();
            return new byte[0];
        }
    }

    /**
     * 填充 word 模板(map 数据格式)
     *
     * @param file word 二进制
     * @param data 要填充的数据
     * @return 组合数据之后的 word 二进制
     */
    public static byte[] fillWordDataByMap(byte[] file, Map<String, Object> data) throws Exception {
        byte[] ret = null;
        if (data == null || data.isEmpty()) {
            return ret;
        }
        try (InputStream is = new ByteArrayInputStream(file); ByteArrayOutputStream out = new ByteArrayOutputStream()) {
            Document doc = new Document(is);
            DocumentBuilder builder = new DocumentBuilder(doc);
            Map<String, String> toData = new HashMap<>();
            for (Map.Entry<String, Object> entry : data.entrySet()) {
                String key = entry.getKey();
                Object value = entry.getValue();
                // 处理表格数据
                if (value instanceof List && !key.equals("checkboxOptions")) {
                    DataTable dataTable = fillListData((List) value, key, builder);
                    doc.getMailMerge().executeWithRegions(dataTable);
                }
                // 图片插入
                else if (value instanceof BufferedImage) {
                    builder.moveToMergeField(key);
                    builder.insertImage((BufferedImage) value);
                }
                // 其他普通字段正常填充
                else {
                    String valueStr = String.valueOf(value);
                    if (value != null && !"null".equals(valueStr)) {
                        toData.put(key, valueStr);
                    }
                }
            }
            // 执行普通字段合并
            String[] fieldNames = new String[toData.size()];
            String[] values = new String[toData.size()];
            int i = 0;
            for (Map.Entry<String, String> entry : toData.entrySet()) {
                fieldNames[i] = entry.getKey();
                values[i] = entry.getValue();
                i++;
            }
            doc.getMailMerge().execute(fieldNames, values);
            doc.save(out, SaveOptions.createSaveOptions(SaveFormat.DOCX));
            ret = out.toByteArray();
        }
        return ret;
    }

    /**
     * 勾选段落中的复选框字段(适用于 Aspose.Words 18.8)
     */
    private static void checkTheCheckbox(Paragraph paragraph) throws Exception {
        FieldCollection fields = paragraph.getRange().getFields();
        int count = fields.getCount();
        for (int i = 0; i < count; i++) {
            com.aspose.words.Field field = fields.get(i);
            if (field.getType() == FieldType.FIELD_FORM_CHECK_BOX) {
                setCheckboxChecked(field, true);
            }
        }
    }

    private static void setCheckboxChecked(com.aspose.words.Field field, boolean checked) throws Exception {
        if (checked) {
            field.setResult("✓");
        } else {
            field.setResult("□");
        }
    }

    /**
     * 封装 list 数据到 word 模板中(word 表格)
     *
     * @param list 数据
     * @param tableName 表格列表变量名称
     * @return word 表格数据 DataTable
     */
    private static DataTable fillListData(List<Object> list, String tableName, DocumentBuilder builder) throws Exception {
        //创建 DataTable,并绑定字段
        DataTable dataTable = new DataTable(tableName);
        for (Object obj : list) {
            //创建 DataRow,封装该行数据
            DataRow dataRow = dataTable.newRow();
            Class<?> objClass = obj.getClass();
            Field[] fields = objClass.getDeclaredFields();
            for (int i = 0; i < fields.length; i++) {
                Field field = fields[i];
                dataTable.getColumns().add(fields[i].getName());
                PropertyDescriptor pd = new PropertyDescriptor(field.getName(), objClass);
                Method method = pd.getReadMethod();
                dataRow.set(i, method.invoke(obj));
            }
            dataTable.getRows().add(dataRow);
        }
        return dataTable;
    }

    /**
     * 加载 license
     * 由于 aspose 是收费的,若没有 license,则会出现水印。
     */
    static {
        try {
            InputStream is = ContractUtil.class.getResourceAsStream("/License.xml");
            License license = new License();
            license.setLicense(is);
        } catch (Exception e) {
            throw new RuntimeException("自动加载 aspose 证书文件失败!");
        }
    }
}

三、测试

main 方法测试,可以根据实际需求改为 response 输出或者上传到存储服务器后返回链接地址。

public static void main(String[] args) throws IOException {
    FillWordDTO fillWordDTO = FillWordDTO.create();
    // 读取模板文件
    byte[] modelByte = Files.readAllBytes(Paths.get("src/main/resources/templates/test.docx"));
    // 调用工具类,获取填充数据后的文件
    byte[] resultByte = ContractUtil.fillWordDataByDomain(modelByte, fillWordDTO);
    // 处理该二进制文件,此处处理为输出到桌面
    File resultFile = new File("demo.docx");
    FileOutputStream fos = new FileOutputStream(resultFile);
    fos.write(resultByte);
    fos.close();
}

四、运行结果

[图片]

五、注意事项

在编辑 word 域代码时,有时会有隐藏的代码导致填充失败。

Found end of mail merge region 'experienceList' that does not match start of mail merge region 'jlList'.

出现以上错误或者想查看域代码,按如下操作: 在 word 中依次点击「文件→选项→高级」,在「显示文档内容」区域勾选「显示域代码而非域值」,找到报错域代码后删除,重新添加域就解决了。

目录

  1. 前言
  2. 一、设置 word 模板
  3. 普通字段
  4. 列表字段
  5. 复选框
  6. 二、代码
  7. 1. 引入依赖
  8. 2. 模板放入项目
  9. 3. 代码
  10. 实体类
  11. 工具类
  12. 三、测试
  13. 四、运行结果
  14. 五、注意事项
  • 💰 8折买阿里云服务器限时8折了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • 量化、算子融合、内存映射:C语言实现AI推理的“三板斧“
  • Flutter for OpenHarmony 集成 dart_openai 实现 AI 对话功能
  • 若依 (RuoYi) 低代码框架深度解析与选型建议
  • 无人机遥感滑坡泥石流图像识别数据集介绍
  • 6 层高速 PCB 设计:立创逻辑派 FPGA-G1 开发板基于立创 EDA 的学习笔记
  • WebUploader 文件上传组件核心功能与配置指南
  • AI Agent 入门:什么是执行式智能体
  • Stable Diffusion ComfyUI 整合包安装与部署指南
  • 网络安全开源靶场 Vulfocus 搭建指南
  • AI入门系列:AI新手必看:人工智能发展历程与现状分析
  • 几款支持万方平台的 AIGC 降重工具实测对比
  • Vite 代理与 Nginx 代理:开发与生产环境的配置差异
  • OpenWRT 配置 SFTP 远程文件传输与安全访问
  • Vue Skills:让 AI 掌握 Vue 最佳实践
  • 大模型驱动文档图像识别技术革新
  • Chatbox AI 桌面客户端功能测评与使用指南
  • C++ 核心基础:STL 容器、位运算与常用算法库
  • 费曼学习法知识助手的 AI 智能体实现案例
  • 攻防世界 MISC 进阶题:图片隐写与 UUencode 解密实战
  • 时序数据库 Apache IoTDB 全链路数据管理、部署与安全特性解读

相关免费在线工具

  • Keycode 信息

    查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

  • Escape 与 Native 编解码

    JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

  • JavaScript / HTML 格式化

    使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

  • JavaScript 压缩与混淆

    Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online