Java Excel 导出:EasyExcel 使用详解
- [一、什么是 EasyExcel](#一、什么是 EasyExcel)
- [二、Maven 依赖安装](#二、Maven 依赖安装)
- [三、Excel 导出工具类设计](#三、Excel 导出工具类设计)
-
- [3.1 设计目标与整体架构](#3.1 设计目标与整体架构)
- [3.2 完整工具类:ExportTool.java](#3.2 完整工具类:ExportTool.java)
- [3.3 核心方法分析](#3.3 核心方法分析)
-
- [3.3.1 简单导出方法](#3.3.1 简单导出方法)
- [3.3.2 带样式导出方法](#3.3.2 带样式导出方法)
- [3.4 样式策略分析](#3.4 样式策略分析)
-
- [3.4.1 表头样式](#3.4.1 表头样式)
- [3.4.2 内容样式](#3.4.2 内容样式)
- [3.5 列宽自适应策略](#3.5 列宽自适应策略)
- [3.6 输出流包装策略](#3.6 输出流包装策略)
- [3.7 ExcelWriter 构建与写入](#3.7 ExcelWriter 构建与写入)
- 四、导出对象类设计
- [五、示例 Controller 导出方法](#五、示例 Controller 导出方法)
在 Java 后端开发中,Excel 导出是非常常见的需求,涉及报表、数据分析或业务导出。
本文将结合 阿里 EasyExcel 库,详细讲解 Excel 导出流程,包括工具类设计、样式、列宽策略、对象映射、输出流处理。
一、什么是 EasyExcel
EasyExcel 是阿里巴巴开源的 Java Excel 处理库,相比 Apache POI 或 JXL:
-
高性能
-
基于 SAX 流式解析和写入,内存占用低
-
可以轻松处理百万行数据导出,不会 OOM
-
-
易用
-
注解式对象映射(
@ExcelProperty) -
只需定义对象类即可生成 Excel 表格
-
-
样式灵活
- 表头样式、内容样式、列宽、合并单元格等都可自定义
-
Web 集成方便
- 可以直接输出到浏览器下载,无需先生成文件
适合企业后台系统、报表系统、统计平台等场景。
二、Maven 依赖安装
在 pom.xml 中引入 EasyExcel:
xml
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>4.0.3</version>
</dependency>
注意事项:
- EasyExcel 依赖 JDK 1.8 及以上版本。
- 在实际项目中,如果出现依赖冲突或版本不兼容导致导出异常 ,可参考该文章进行排查和解决:EasyExcel 依赖冲突解决方案。
三、Excel 导出工具类设计
3.1 设计目标与整体架构
ExportTool 工具类主要实现了两个导出方式:
-
简单导出:快速将数据列表写入 Excel,无样式、无列宽控制。
-
带样式导出:支持自定义表头样式、内容样式和列宽自适应。
核心设计特点:
-
泛型支持 :通过
<T>+Class<T>,工具类可导出任意类型的数据对象。 -
流安全处理 :通过
NoCloseOutputStream包装输出流,避免 EasyExcel 自动关闭HttpServletResponse。 -
策略模式:样式、列宽分别封装为独立策略,便于扩展。
-
日志和异常管理 :导出过程中的异常统一记录并封装为
ServiceException,方便上层调用处理。
工具类结构可以理解为三层:
响应流处理 → ExcelWriter 构建 → 样式 & 列宽策略 → 数据写入
3.2 完整工具类:ExportTool.java
java
@Slf4j
public class ExportTool {
/**
* 导出 Excel 文件(简单导出)
*
* @param dataList 导出的数据列表
* @param clazz 数据类型(Excel 实体类)
* @param fileName 导出的文件名(不带扩展名)
* @param <T> 数据泛型
*/
public static <T> void exportExcel(List<T> dataList, Class<T> clazz, String fileName) {
if (dataList == null || dataList.isEmpty()) {
log.warn("[ExportTool] 导出数据为空: {}", fileName);
}
try {
HttpServletResponse response = WebTool.getResponse();
// 使用复用的响应流设置方法
setupResponse(response, fileName + ".xlsx");
try (ServletOutputStream out = response.getOutputStream()) {
// 简单导出,不增加样式或列宽限制
EasyExcel.write(out, clazz)
.autoCloseStream(true)
.sheet("数据")
.doWrite(dataList);
}
log.info("[ExportTool] 导出成功: {}, 共 {} 条记录", fileName, dataList.size());
} catch (Exception e) {
log.error("[ExportTool] 导出失败: {}", fileName, e);
throw new ServiceException("导出失败,请稍后重试");
}
}
/**
* 导出 Excel 文件(带表头样式)
*
* @param dataList 数据列表
* @param clazz 数据类型(Excel 实体类)
* @param fileName 导出文件名(包含 .xlsx)
* @param <T> 数据类型
*/
public static <T> void exportExcelWithStyle(List<T> dataList, Class<T> clazz, String fileName) {
HttpServletResponse response = WebTool.getResponse();
try {
// 设置响应头
setupResponse(response, fileName);
// 获取输出流并防止 EasyExcel 关闭响应流
ServletOutputStream out = response.getOutputStream();
NoCloseOutputStream noCloseOut = new NoCloseOutputStream(out);
// 创建 ExcelWriter
ExcelWriter excelWriter = EasyExcel.write(noCloseOut, clazz)
// 注册样式策略
.registerWriteHandler(new HorizontalCellStyleStrategy(createHeadStyle(), createContentStyle()))
// 注册列宽自适应策略
.registerWriteHandler(createAutoWidthStrategy())
.build();
// 创建 Sheet
WriteSheet writeSheet = EasyExcel.writerSheet("导出数据").build();
// 写入数据
excelWriter.write(dataList, writeSheet);
// 完成写入
excelWriter.finish();
log.info("[ExportTool] 导出成功: {}", fileName);
} catch (Exception e) {
log.error("[ExportTool] 导出失败: {}", fileName, e);
throw new ServiceException("导出 Excel 失败");
}
}
/**
* 设置 HttpServletResponse 响应头
*/
private static void setupResponse(HttpServletResponse response, String fileName) throws Exception {
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.toString());
response.setHeader("Content-Disposition", "attachment;filename=" + encodedFileName);
}
/**
* 创建表头样式
*/
private static WriteCellStyle createHeadStyle() {
WriteCellStyle headStyle = new WriteCellStyle();
// 设置白底(不填充颜色)
headStyle.setFillForegroundColor(null);
headStyle.setFillPatternType(FillPatternType.NO_FILL);
// 设置字体
WriteFont headFont = new WriteFont();
headFont.setFontHeightInPoints((short) 11);
headFont.setFontName("微软雅黑");
headFont.setBold(true);
headFont.setColor(IndexedColors.PALE_BLUE.getIndex());
headStyle.setWriteFont(headFont);
// 设置居中与自动换行
headStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
headStyle.setVerticalAlignment(VerticalAlignment.CENTER);
headStyle.setWrapped(true);
// 设置无边框
headStyle.setBorderLeft(BorderStyle.NONE);
headStyle.setBorderRight(BorderStyle.NONE);
headStyle.setBorderTop(BorderStyle.NONE);
headStyle.setBorderBottom(BorderStyle.NONE);
return headStyle;
}
/**
* 创建内容样式
*/
private static WriteCellStyle createContentStyle() {
WriteCellStyle contentStyle = new WriteCellStyle();
// 设置字体
WriteFont contentFont = new WriteFont();
contentFont.setFontHeightInPoints((short) 11);
contentFont.setFontName("等线");
contentStyle.setWriteFont(contentFont);
// 内容右对齐,垂直居中,自动换行
contentStyle.setHorizontalAlignment(HorizontalAlignment.RIGHT);
contentStyle.setVerticalAlignment(VerticalAlignment.CENTER);
contentStyle.setWrapped(true);
return contentStyle;
}
/**
* 列宽自适应策略(根据内容长度自动调整列宽,限制最小 15、最大 20 字符)
*/
private static AbstractColumnWidthStyleStrategy createAutoWidthStrategy() {
return new AbstractColumnWidthStyleStrategy() {
private final Map<Integer, Integer> columnWidthMap = new HashMap<>();
@Override
protected void setColumnWidth(WriteSheetHolder writeSheetHolder,
List<WriteCellData<?>> cellDataList,
Cell cell,
Head head,
Integer relativeRowIndex,
Boolean isHead) {
if (cell == null || cell.getCellType() != CellType.STRING) return;
String value = cell.getStringCellValue();
if (value == null) return;
Sheet sheet = writeSheetHolder.getSheet();
int columnIndex = cell.getColumnIndex();
// 根据 UTF-8 字节长度计算列宽
int length = value.getBytes(StandardCharsets.UTF_8).length;
int minWidth = 15 * 256;
int maxWidth = 20 * 256;
int width = Math.max(minWidth, Math.min(length * 256 + 200, maxWidth));
// 如果当前列宽大于记录的最大宽度,则更新列宽
Integer maxWidthRecorded = columnWidthMap.getOrDefault(columnIndex, 0);
if (width > maxWidthRecorded) {
columnWidthMap.put(columnIndex, width);
sheet.setColumnWidth(columnIndex, width);
}
// 设置自动换行样式
CellStyle style = sheet.getWorkbook().createCellStyle();
style.cloneStyleFrom(cell.getCellStyle());
style.setWrapText(true);
cell.setCellStyle(style);
}
};
}
/**
* 包装 Servlet 输出流,不关闭底层流
*/
public static class NoCloseOutputStream extends FilterOutputStream {
public NoCloseOutputStream(OutputStream out) {
super(out);
}
@Override
public void close() throws IOException {
flush(); // 不关闭底层流,仅刷新
}
}
}
3.3 核心方法分析
3.3.1 简单导出方法
java
public static <T> void exportExcel(List<T> dataList, Class<T> clazz, String fileName)
功能:快速生成 Excel 文件,无样式和列宽控制。
执行流程:
-
数据判空:日志告警数据为空情况。
-
获取响应流 :通过
WebTool.getResponse()获取 HttpServletResponse。 -
设置响应头:设置 MIME 类型和 Content-Disposition,保证浏览器下载文件。
-
EasyExcel 写入 :调用
EasyExcel.write(out, clazz).sheet("数据").doWrite(dataList),直接写入数据列表。 -
异常处理 :统一捕获异常,抛出
ServiceException。
特点:
-
接口简单,使用泛型可适配任意实体类
-
无额外样式处理,适合数据量大或快速导出场景
3.3.2 带样式导出方法
java
public static <T> void exportExcelWithStyle(List<T> dataList, Class<T> clazz, String fileName)
功能 :在 Excel 中生成 自定义表头、内容样式,并自动调整列宽。
执行流程:
-
获取响应流并设置响应头。
-
包装输出流 :使用
NoCloseOutputStream防止 EasyExcel 自动关闭底层流。 -
构建 ExcelWriter:
-
注册 表头样式 + 内容样式 (
HorizontalCellStyleStrategy) -
注册 列宽自适应策略 (
AbstractColumnWidthStyleStrategy)
-
-
创建 Sheet :定义名称,例如
"导出数据"。 -
写入数据 :
excelWriter.write(dataList, writeSheet) -
完成写入 :调用
excelWriter.finish()完成 Excel 写入流程。 -
异常处理 :统一日志记录,抛出
ServiceException。
特点:
- 样式和列宽可独立调整,便于复用和扩展
- 保证浏览器端下载体验不受影响
- 对长文本和数字型列提供自动换行和右对齐
3.4 样式策略分析
3.4.1 表头样式
java
WriteCellStyle headStyle = createHeadStyle();
特点:
-
字体:
微软雅黑,加粗,颜色淡蓝 -
对齐方式:水平居中、垂直居中
-
自动换行,保证多行表头显示完整
-
无边框、白底,视觉简洁
设计思路:
-
独立封装表头样式,便于在多个导出场景中复用
-
样式可通过
HorizontalCellStyleStrategy与内容样式统一管理
3.4.2 内容样式
java
WriteCellStyle contentStyle = createContentStyle();
特点:
-
字体:
等线 -
水平右对齐(适合数字、金额)、垂直居中
-
自动换行,保证长文本显示
-
与表头样式区分,形成信息层次感
设计思路:
-
独立封装内容样式,提高可维护性
-
通过策略模式应用于 ExcelWriter,无需重复设置
3.5 列宽自适应策略
java
AbstractColumnWidthStyleStrategy autoWidthStrategy = createAutoWidthStrategy();
实现逻辑:
-
遍历每个单元格内容,获取 UTF-8 字节长度。
-
根据长度计算列宽,设置最小 15、最大 20 字符。
-
对比历史最大列宽,确保列宽不会缩小。
-
设置单元格自动换行,保证内容完整显示。
优点:
-
避免手动设置每列宽度
-
支持中英文混合内容,保证可读性
-
与样式策略解耦,可独立替换或增强
注意:UTF-8 字节长度计算对不同字符可能略有偏差,中英文混合时列宽可能需微调
3.6 输出流包装策略
java
public static class NoCloseOutputStream extends FilterOutputStream
作用:
-
EasyExcel 在
finish()默认关闭流 -
包装流后仅执行
flush(),不关闭底层HttpServletResponse -
保证浏览器下载文件正常,不中断响应
优点:
-
简单、安全
-
兼容大多数 Web 导出场景
3.7 ExcelWriter 构建与写入
java
ExcelWriter excelWriter = EasyExcel.write(noCloseOut, clazz)
.registerWriteHandler(styleStrategy)
.registerWriteHandler(autoWidthStrategy)
.build();
分析:
-
解耦样式和列宽策略,便于单独扩展
-
批量写入数据,可适配大数据场景
-
支持自定义 Sheet 名称
-
可进一步扩展:冻结首行、单元格合并、插入公式等
四、导出对象类设计
java
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ExportUserVO {
@ExcelProperty("姓名")
private String userName;
@ExcelProperty("ID")
private Long id;
@ExcelProperty("使用个数")
private Integer useCount;
}
-
每个字段使用
@ExcelProperty注解列名 -
数据类型支持 String、Integer、Double、Long 等常用类型
-
对象类清晰定义列名和数据结构
五、示例 Controller 导出方法
java
@GetMapping("/export")
public void exportUserExcel(HttpServletResponse response) {
List<ExportUserVO> exportList = Arrays.asList(
new ExportUserVO("张三", 1001L, 5),
new ExportUserVO("李四", 1002L, 3)
);
String fileName = "用户导出.xlsx";
ExportTool.exportExcelWithStyle(response, exportList, ExportUserVO.class, fileName);
}
-
浏览器访问
/export即可直接下载 Excel -
充分体现工具类的通用性和可复用性
导出Excel效果 :
