杰瑞科技汇

Java Excel模板导出怎么实现?

  • 样式和布局灵活:设计师可以直接用 Excel 设计出最终报表的样式(合并单元格、颜色、字体、边框、图片位置等),开发者无需在代码中编写复杂的样式设置。
  • 开发效率高:开发者只需关注数据的填充,无需关心复杂的 Excel 格式。
  • 维护方便:当报表样式需要调整时,只需修改 Excel 模板文件即可,无需重新编译和部署代码。

下面我将详细介绍几种主流的实现方法,并提供详细的代码示例。

Java Excel模板导出怎么实现?-图1
(图片来源网络,侵删)

核心思想

模板导出的核心流程通常如下:

  1. 准备模板文件:创建一个 .xlsx.xls 文件,在需要填充数据的地方使用特定的占位符(如 ${name}, #{list})进行标记。
  2. Java 代码读取模板:使用 Java 库加载这个模板文件。
  3. 数据准备:从数据库或其他数据源获取需要导出的数据,并将其组织成 Java 对象(如 List<Map> 或自定义的实体类 List<Entity>)。
  4. 数据替换:遍历数据,将模板中的占位符替换为实际的数据。
  5. 生成新文件并输出:将填充好数据的新 Excel 文件写入到输出流(如 OutputStream),最终供用户下载。

使用 Apache POI (XSSF) + 手动替换 (推荐用于简单场景)

这种方法最直接,不需要额外的第三方库,但需要自己处理占位符的替换逻辑。

准备模板文件 (template.xlsx)

创建一个 Excel 文件,template.xlsx如下:

姓名 年龄 部门
${name1} ${age1} ${dept1}
${name2} ${age2} ${dept2}
... ... ...
总计 ${totalAge}

这里的 就是我们的占位符。

Java Excel模板导出怎么实现?-图2
(图片来源网络,侵删)

添加 Maven 依赖

<dependencies>
    <!-- Apache POI for .xlsx files -->
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi-ooxml</artifactId>
        <version>5.2.3</version> <!-- 使用较新稳定版本 -->
    </dependency>
</dependencies>

Java 实现代码

import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.FileInputStream;
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 PoiTemplateExporter {
    public static void main(String[] args) {
        // 1. 准备模板文件路径和输出文件路径
        String templatePath = "D:/temp/template.xlsx";
        String outputPath = "D:/temp/output.xlsx";
        // 2. 准备要填充的数据
        List<Map<String, Object>> dataList = new ArrayList<>();
        dataList.add(createDataMap("张三", 28, "技术部"));
        dataList.add(createDataMap("李四", 32, "市场部"));
        dataList.add(createDataMap("王五", 25, "人事部"));
        // 计算总年龄
        int totalAge = dataList.stream().mapToInt(map -> (int) map.get("age")).sum();
        // 3. 执行导出
        try {
            exportWithPoi(templatePath, outputPath, dataList, totalAge);
            System.out.println("Excel 文件导出成功!路径: " + outputPath);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public static void exportWithPoi(String templatePath, String outputPath, 
                                    List<Map<String, Object>> dataList, int totalAge) throws IOException {
        // a. 加载模板文件
        try (FileInputStream fis = new FileInputStream(templatePath);
             Workbook workbook = new XSSFWorkbook(fis);
             FileOutputStream fos = new FileOutputStream(outputPath)) {
            Sheet sheet = workbook.getSheetAt(0); // 获取第一个工作表
            // b. 替换单行数据占位符 (从第二行开始,假设第一行是表头)
            int startRow = 1; // 数据起始行
            for (int i = 0; i < dataList.size(); i++) {
                Map<String, Object> data = dataList.get(i);
                Row row = sheet.getRow(startRow + i);
                if (row == null) continue;
                // 替换 ${name1}, ${age1}, ${dept1} 等
                replaceCellData(row.getCell(0), data.get("name"));
                replaceCellData(row.getCell(1), data.get("age"));
                replaceCellData(row.getCell(2), data.get("dept"));
            }
            // c. 替换单个值占位符 (如总计)
            // 假设总计在第 data.size() + 2 行
            Row totalRow = sheet.getRow(startRow + dataList.size() + 1);
            if (totalRow != null) {
                replaceCellData(totalRow.getCell(1), totalAge);
            }
            // d. 写入输出流
            workbook.write(fos);
        }
    }
    private static Map<String, Object> createDataMap(String name, int age, String dept) {
        Map<String, Object> map = new HashMap<>();
        map.put("name", name);
        map.put("age", age);
        map.put("dept", dept);
        return map;
    }
    private static void replaceCellData(Cell cell, Object value) {
        if (cell == null || value == null) {
            return;
        }
        // 根据数据类型设置单元格值
        if (value instanceof String) {
            cell.setCellValue((String) value);
        } else if (value instanceof Number) {
            cell.setCellValue(((Number) value).doubleValue());
        } else if (value instanceof Boolean) {
            cell.setCellValue((Boolean) value);
        } else {
            cell.setCellValue(value.toString());
        }
    }
}

优点

  • 无需额外依赖,POI 是 Java 操作 Excel 的标准库。
  • 灵活性高,可以精确控制每一个单元格。

缺点

  • 代码复杂:需要手动遍历行和列,处理逻辑繁琐。
  • 性能一般:对于大数据量,循环替换效率不高。
  • 难以处理复杂列表:如果模板中有一个动态高度的表格,用 POI 手动实现会非常困难。

使用 EasyExcel (阿里巴巴出品,强烈推荐)

EasyExcel 是阿里巴巴开源的一个处理 Excel 的项目,它对 POI 做了大量优化,内存占用极低,并且提供了非常方便的模板填充功能。

准备模板文件 (easy_template.xlsx)

模板文件和方法一类似,但 EasyExcel 推荐使用 作为占位符,更清晰。

Java Excel模板导出怎么实现?-图3
(图片来源网络,侵删)
姓名 年龄 部门
{{name}} {{age}} {{dept}}
总计 {{totalAge}}

添加 Maven 依赖

<dependencies>
    <!-- EasyExcel -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>easyexcel</artifactId>
        <version>3.1.1</version> <!-- 使用较新稳定版本 -->
    </dependency>
</dependencies>

Java 实现代码

EasyExcel 的模板填充极其简洁。

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.metadata.WriteTable;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class EasyExcelTemplateExporter {
    public static void main(String[] args) {
        // 1. 准备模板文件路径和输出文件路径
        String templatePath = "D:/temp/easy_template.xlsx";
        String outputPath = "D:/temp/easy_output.xlsx";
        // 2. 准备要填充的数据
        List<Map<String, Object>> dataList = new ArrayList<>();
        dataList.add(createDataMap("张三", 28, "技术部"));
        dataList.add(createDataMap("李四", 32, "市场部"));
        dataList.add(createDataMap("王五", 25, "人事部"));
        int totalAge = dataList.stream().mapToInt(map -> (int) map.get("age")).sum();
        // 准备单个值的 Map
        Map<String, Object> summaryMap = new HashMap<>();
        summaryMap.put("totalAge", totalAge);
        // 3. 执行导出
        try (ExcelWriter excelWriter = EasyExcel.write(outputPath).withTemplate(templatePath).build()) {
            WriteSheet writeSheet = EasyExcel.writerSheet().build();
            // 填充列表数据
            // EasyExcel 会自动识别模板中的列表区域,并循环填充
            excelWriter.fill(dataList, writeSheet);
            // 填充单个值数据
            // 为了填充单个值,需要指定一个表格,通常模板中只有一个表格,索引为0
            WriteTable writeTable = EasyExcel.writerTable(0).build();
            excelWriter.fill(summaryMap, writeTable, writeSheet);
            System.out.println("EasyExcel 文件导出成功!路径: " + outputPath);
        }
    }
    private static Map<String, Object> createDataMap(String name, int age, String dept) {
        Map<String, Object> map = new HashMap<>();
        map.put("name", name);
        map.put("age", age);
        map.put("dept", dept);
        return map;
    }
}

优点

  • 代码极其简洁:几行代码即可完成复杂的填充操作。
  • 性能卓越:基于 SAX 模式读取,内存占用非常低,适合处理大数据量。
  • 功能强大:自动处理列表填充、图片填充、公式填充等。
  • 社区活跃:阿里巴巴维护,文档和示例丰富。

缺点

  • 引入了新的第三方依赖。

使用 JXLS (专门为 Excel 模板设计)

JXLS (Java Excel Template Library) 是一个专门为 Excel 模板设计的库,它使用类似 JSP/Velocity 的语法来定义模板,非常灵活。

准备模板文件 (jxls_template.xlsx)

JXLS 的语法更丰富,

  • JXLS 语法: jx:area 定义一个区域,jx:each 循环一个集合。
  • 占位符: ${person.name}
姓名 年龄 部门
员工列表
${person.name} ${person.age} ${person.dept}
总计 ${totalAge}

你还需要在 Excel 的一个隐藏单元格(比如最后一个)里放置一个指令区域:

jx:area(lastCell="D4")
    jx:each(items="employees" var="person" lastCell="D4")
        ${person.name}    ${person.age}    ${person.dept}
    /jx:each
/jx:area

这个指令告诉 JXLS 从 A1 到 D4 的区域是一个循环区域,数据源是 employees 变量。

添加 Maven 依赖

<dependencies>
    <!-- JXLS -->
    <dependency>
        <groupId>org.jxls</groupId>
        <artifactId>jxls</artifactId>
        <version>2.8.1</version>
    </dependency>
    <dependency>
        <groupId>org.jxls</groupId>
        <artifactId>jxls-poi</artifactId>
        <version>2.8.1</version>
    </dependency>
</dependencies>

Java 实现代码

import org.jxls.common.Context;
import org.jxls.util.JxlsHelper;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
public class JxlsTemplateExporter {
    public static void main(String[] args) {
        // 1. 准备模板文件路径和输出文件路径
        String templatePath = "D:/temp/jxls_template.xlsx";
        String outputPath = "D:/temp/jxls_output.xlsx";
        // 2. 准备要填充的数据
        List<Employee> employees = new ArrayList<>();
        employees.add(new Employee("张三", 28, "技术部"));
        employees.add(new Employee("李四", 32, "市场部"));
        employees.add(new Employee("王五", 25, "人事部"));
        int totalAge = employees.stream().mapToInt(Employee::getAge).sum();
        // 3. 执行导出
        try (InputStream is = JxlsTemplateExporter.class.getResourceAsStream(templatePath);
             OutputStream os = new FileOutputStream(outputPath)) {
            // 创建 JXLS 上下文,并放入数据
            Context context = new Context();
            context.putVar("employees", employees);
            context.putVar("totalAge", totalAge);
            // 执行填充
            JxlsHelper.getInstance().processTemplate(is, os, context);
            System.out.println("JXLS 文件导出成功!路径: " + outputPath);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public static class Employee {
        private String name;
        private int age;
        private String dept;
        public Employee(String name, int age, String dept) {
            this.name = name;
            this.age = age;
            this.dept = dept;
        }
        // Getters
        public String getName() { return name; }
        public int getAge() { return age; }
        public String getDept() { return dept; }
    }
}

优点

  • 模板语法强大:支持复杂的逻辑判断、循环、宏等,功能非常灵活。
  • 关注点分离:模板文件中包含了业务逻辑,非常适合复杂的报表需求。

缺点

  • 学习曲线:需要学习 JXLS 自己的模板语法。
  • 维护性:将逻辑放在 Excel 文件中,对于不熟悉开发的业务人员来说,维护可能比较困难。
  • 社区相对较小:不如 POI 和 EasyExcel 活跃。

总结与对比

特性 Apache POI (手动替换) EasyExcel JXLS
易用性 低,代码繁琐 极高,API 简洁 中等,需学习模板语法
性能 一般 极佳,内存占用低 一般
功能 基础功能强大,但实现复杂 非常全面,支持列表、图片等 最灵活,支持复杂逻辑
适用场景 简单、静态报表,或不想引入新库时 绝大多数场景,尤其是 Web 应用和大数据量导出 复杂模板,需要将业务逻辑嵌入模板时
推荐度 ⭐⭐ ⭐⭐⭐

最终建议

对于绝大多数 Java 项目,强烈推荐使用 EasyExcel,它在易用性、性能和功能之间取得了最佳平衡,是当前 Java Excel 操作领域的事实标准。

只有在模板逻辑极其复杂,且希望将部分逻辑放在 Excel 模板文件中管理时,才考虑使用 JXLS。

如果只是一个非常简单的、一次性的导出任务,且不想引入 EasyExcel 这样的新依赖,那么使用 Apache POI 手动替换也是一个可行的选择。

分享:
扫描分享到社交APP
上一篇
下一篇