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

核心思想
模板导出的核心流程通常如下:
- 准备模板文件:创建一个
.xlsx或.xls文件,在需要填充数据的地方使用特定的占位符(如${name},#{list})进行标记。 - Java 代码读取模板:使用 Java 库加载这个模板文件。
- 数据准备:从数据库或其他数据源获取需要导出的数据,并将其组织成 Java 对象(如
List<Map>或自定义的实体类List<Entity>)。 - 数据替换:遍历数据,将模板中的占位符替换为实际的数据。
- 生成新文件并输出:将填充好数据的新 Excel 文件写入到输出流(如
OutputStream),最终供用户下载。
使用 Apache POI (XSSF) + 手动替换 (推荐用于简单场景)
这种方法最直接,不需要额外的第三方库,但需要自己处理占位符的替换逻辑。
准备模板文件 (template.xlsx)
创建一个 Excel 文件,template.xlsx如下:
| 姓名 | 年龄 | 部门 |
|---|---|---|
| ${name1} | ${age1} | ${dept1} |
| ${name2} | ${age2} | ${dept2} |
| ... | ... | ... |
| 总计 | ${totalAge} |
这里的 就是我们的占位符。

添加 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 推荐使用 作为占位符,更清晰。

| 姓名 | 年龄 | 部门 |
|---|---|---|
| {{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 手动替换也是一个可行的选择。
