深入解析:Java Excel 导出:EasyExcel 使用详解

深入解析:Java Excel 导出:EasyExcel 使用详解

Java Excel 导出:EasyExcel 使用详解一、什么是 EasyExcel二、Maven 依赖安装三、Excel 导出工具类设计3.1 设计目标与整体架构3.2 完整工具类:ExportTool.java3.3 核心方法分析3.3.1 简单导出方法3.3.2 带样式导出方法3.4 样式策略分析3.4.1 表头样式3.4.2 内容样式3.5 列宽自适应策略3.6 输出流包装策略3.7 ExcelWriter 构建与写入四、导出对象类设计五、示例 Controller 导出方法

在 Java 后端开发中,Excel 导出是非常常见的需求,涉及报表、数据分析或业务导出。

本文将结合 阿里 EasyExcel 库,详细讲解 Excel 导出流程,包括工具类设计、样式、列宽策略、对象映射、输出流处理。

一、什么是 EasyExcelEasyExcel 是阿里巴巴开源的 Java Excel 处理库,相比 Apache POI 或 JXL:

高性能

基于 SAX 流式解析和写入,内存占用低

可以轻松处理百万行数据导出,不会 OOM

易用

注解式对象映射(@ExcelProperty)

只需定义对象类即可生成 Excel 表格

样式灵活

表头样式、内容样式、列宽、合并单元格等都可自定义Web 集成方便

可以直接输出到浏览器下载,无需先生成文件适合企业后台系统、报表系统、统计平台等场景。

二、Maven 依赖安装在 pom.xml 中引入 EasyExcel:

com.alibaba

easyexcel

4.0.3

注意事项:

EasyExcel 依赖 JDK 1.8 及以上版本。在实际项目中,如果出现依赖冲突或版本不兼容导致导出异常,可参考该文章进行排查和解决:EasyExcel 依赖冲突解决方案。三、Excel 导出工具类设计3.1 设计目标与整体架构ExportTool 工具类主要实现了两个导出方式:

简单导出:快速将数据列表写入 Excel,无样式、无列宽控制。

带样式导出:支持自定义表头样式、内容样式和列宽自适应。

核心设计特点:

泛型支持:通过 + Class,工具类可导出任意类型的数据对象。

流安全处理:通过 NoCloseOutputStream 包装输出流,避免 EasyExcel 自动关闭 HttpServletResponse。

策略模式:样式、列宽分别封装为独立策略,便于扩展。

日志和异常管理:导出过程中的异常统一记录并封装为 ServiceException,方便上层调用处理。

工具类结构可以理解为三层:

响应流处理 → ExcelWriter 构建 → 样式 & 列宽策略 → 数据写入

3.2 完整工具类:ExportTool.java

@Slf4j

public class ExportTool {

/**

* 导出 Excel 文件(简单导出)

*

* @param dataList 导出的数据列表

* @param clazz 数据类型(Excel 实体类)

* @param fileName 导出的文件名(不带扩展名)

* @param 数据泛型

*/

public static void exportExcel(List dataList, Class 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 数据类型

*/

public static void exportExcelWithStyle(List dataList, Class 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 columnWidthMap = new HashMap<>();

@Override

protected void setColumnWidth(WriteSheetHolder writeSheetHolder,

List> 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 简单导出方法

public static void exportExcel(List dataList, Class clazz, String fileName)

功能:快速生成 Excel 文件,无样式和列宽控制。

执行流程:

数据判空:日志告警数据为空情况。

获取响应流:通过 WebTool.getResponse() 获取 HttpServletResponse。

设置响应头:设置 MIME 类型和 Content-Disposition,保证浏览器下载文件。

EasyExcel 写入:调用 EasyExcel.write(out, clazz).sheet("数据").doWrite(dataList),直接写入数据列表。

异常处理:统一捕获异常,抛出 ServiceException。

特点:

接口简单,使用泛型可适配任意实体类

无额外样式处理,适合数据量大或快速导出场景

3.3.2 带样式导出方法

public static void exportExcelWithStyle(List dataList, Class clazz, String fileName)

功能:在 Excel 中生成 自定义表头、内容样式,并自动调整列宽。

执行流程:

获取响应流并设置响应头。

包装输出流:使用 NoCloseOutputStream 防止 EasyExcel 自动关闭底层流。

构建 ExcelWriter:

注册 表头样式 + 内容样式 (HorizontalCellStyleStrategy)

注册 列宽自适应策略 (AbstractColumnWidthStyleStrategy)

创建 Sheet:定义名称,例如 "导出数据"。

写入数据:excelWriter.write(dataList, writeSheet)

完成写入:调用 excelWriter.finish() 完成 Excel 写入流程。

异常处理:统一日志记录,抛出 ServiceException。

特点:

样式和列宽可独立调整,便于复用和扩展保证浏览器端下载体验不受影响对长文本和数字型列提供自动换行和右对齐3.4 样式策略分析3.4.1 表头样式

WriteCellStyle headStyle = createHeadStyle();

特点:

字体:微软雅黑,加粗,颜色淡蓝

对齐方式:水平居中、垂直居中

自动换行,保证多行表头显示完整

无边框、白底,视觉简洁

设计思路:

独立封装表头样式,便于在多个导出场景中复用

样式可通过 HorizontalCellStyleStrategy 与内容样式统一管理

3.4.2 内容样式

WriteCellStyle contentStyle = createContentStyle();

特点:

字体:等线

水平右对齐(适合数字、金额)、垂直居中

自动换行,保证长文本显示

与表头样式区分,形成信息层次感

设计思路:

独立封装内容样式,提高可维护性

通过策略模式应用于 ExcelWriter,无需重复设置

3.5 列宽自适应策略

AbstractColumnWidthStyleStrategy autoWidthStrategy = createAutoWidthStrategy();

实现逻辑:

遍历每个单元格内容,获取 UTF-8 字节长度。

根据长度计算列宽,设置最小 15、最大 20 字符。

对比历史最大列宽,确保列宽不会缩小。

设置单元格自动换行,保证内容完整显示。

优点:

避免手动设置每列宽度

支持中英文混合内容,保证可读性

与样式策略解耦,可独立替换或增强

注意:UTF-8 字节长度计算对不同字符可能略有偏差,中英文混合时列宽可能需微调

3.6 输出流包装策略

public static class NoCloseOutputStream extends FilterOutputStream

作用:

EasyExcel 在 finish() 默认关闭流

包装流后仅执行 flush(),不关闭底层 HttpServletResponse

保证浏览器下载文件正常,不中断响应

优点:

简单、安全

兼容大多数 Web 导出场景

3.7 ExcelWriter 构建与写入

ExcelWriter excelWriter = EasyExcel.write(noCloseOut, clazz)

.registerWriteHandler(styleStrategy)

.registerWriteHandler(autoWidthStrategy)

.build();

分析:

解耦样式和列宽策略,便于单独扩展

批量写入数据,可适配大数据场景

支持自定义 Sheet 名称

可进一步扩展:冻结首行、单元格合并、插入公式等

四、导出对象类设计

@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 导出方法

@GetMapping("/export")

public void exportUserExcel(HttpServletResponse response) {

List exportList = Arrays.asList(

new ExportUserVO("张三", 1001L, 5),

new ExportUserVO("李四", 1002L, 3)

);

String fileName = "用户导出.xlsx";

ExportTool.exportExcelWithStyle(response, exportList, ExportUserVO.class, fileName);

}

浏览器访问 /export 即可直接下载 Excel

充分体现工具类的通用性和可复用性

导出Excel效果:

相关推荐