当前位置: 首页 > news >正文

Java Excel 导出:EasyExcel 使用详解

Java Excel 导出:EasyExcel 使用详解

  • 一、什么是 EasyExcel
  • 二、Maven 依赖安装
  • 三、Excel 导出工具类设计
    • 3.1 设计目标与整体架构
    • 3.2 完整工具类:ExportTool.java
    • 3.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 导出流程,包括工具类设计、样式、列宽策略、对象映射、输出流处理


一、什么是 EasyExcel

EasyExcel 是阿里巴巴开源的 Java Excel 处理库,相比 Apache POI 或 JXL:

  1. 高性能

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

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

  2. 易用

    • 注解式对象映射(@ExcelProperty

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

  3. 样式灵活

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

    • 可以直接输出到浏览器下载,无需先生成文件

适合企业后台系统、报表系统、统计平台等场景。


二、Maven 依赖安装

pom.xml 中引入 EasyExcel:

<dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>4.0.3</version>
</dependency>

注意事项:

  1. EasyExcel 依赖 JDK 1.8 及以上版本
  2. 在实际项目中,如果出现依赖冲突或版本不兼容导致导出异常,可参考该文章进行排查和解决:EasyExcel 依赖冲突解决方案。

三、Excel 导出工具类设计

3.1 设计目标与整体架构

ExportTool 工具类主要实现了两个导出方式:

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

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

核心设计特点

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

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

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

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

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

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

3.2 完整工具类:ExportTool.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);// 创建 ExcelWriterExcelWriter excelWriter = EasyExcel.write(noCloseOut, clazz)// 注册样式策略.registerWriteHandler(new HorizontalCellStyleStrategy(createHeadStyle(), createContentStyle()))// 注册列宽自适应策略.registerWriteHandler(createAutoWidthStrategy()).build();// 创建 SheetWriteSheet 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<>();@Overrideprotected 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);}@Overridepublic void close() throws IOException {flush(); // 不关闭底层流,仅刷新}}
}

3.3 核心方法分析

3.3.1 简单导出方法

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

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

执行流程

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

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

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

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

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

特点

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

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


3.3.2 带样式导出方法

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

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

执行流程

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

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

  3. 构建 ExcelWriter

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

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

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

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

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

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

特点

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

3.4 样式策略分析

3.4.1 表头样式

WriteCellStyle headStyle = createHeadStyle();

特点

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

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

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

  • 无边框、白底,视觉简洁

设计思路

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

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

3.4.2 内容样式

WriteCellStyle contentStyle = createContentStyle();

特点

  • 字体:等线

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

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

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

设计思路

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

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


3.5 列宽自适应策略

AbstractColumnWidthStyleStrategy autoWidthStrategy = createAutoWidthStrategy();

实现逻辑

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

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

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

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

优点

  • 避免手动设置每列宽度

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

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

注意: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<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效果
在这里插入图片描述

http://www.dtcms.com/a/596629.html

相关文章:

  • 【SOMEIP】【R24-11】【需求翻译】[RS_SOMEIP_00002]-[RS_SOMEIP_00004]
  • VMware无法将网络更改为桥接状态:没有未桥接的主机网络适配器
  • maven打包问题/ClassNotFoundException异常
  • 告别局域网限制!Windows快速部署Docsify技术文档站点,搭配cpolar内网穿透实现公网随时随地访问
  • Python每日一练---第十二天:验证回文串
  • 【Docker】Dockerfile自定义镜像
  • 1.3 Spring的入门程序
  • 网站内的链接怎么做修改wordpress 表格
  • 企业网站源码是什么网站内链怎么优化
  • FPGA-zynq PS与PL的交互(一)
  • 做电影网站需要施工企业会计核算及常用会计分录
  • CONFIG_TRACEPOINTS和CONFIG_FTRACE的作用
  • LeetCode 热题 100——哈希——字母异位词分组
  • MATLAB 计算两点直线方程(叉乘)
  • 系统分析师-信息安全-通信与网络安全技术系统访问控制技术
  • 25年11月软考架构真题《论无服务器架构(Serverless)》考后复盘总结
  • SMamba: 基于稀疏Mamba的事件相机目标检测
  • 怎么给自己制作一个网站php整站最新版本下载
  • 饰品网站模版哪种网站
  • 耐达讯自动化Profibus光纤模块:智能仪表的“生命线”,极端环境通信无忧!
  • C++入门(算法) - 习题
  • 校验热稳定,裸导体宜采用主保护动作时间加相应断路器开断时间。电气设备宜采用后备保护动作时间加相应断路器开断时间。
  • 【Java SE 基础学习打卡】10 JDK 下载与安装
  • 【SqlServer】日志文件无法收缩的解决方法
  • 拟牛顿法的数学原理:正定性、合理性与割线约束
  • 解决 Chrome 下载 `.crx` 文件被自动删除及“无法安装扩展程序,因为它使用了不受支持的清单版本”问题
  • 网站图片翻页效果如何做网站开发有哪些服务器
  • o2o网站建设如何上海网站建设服
  • 【agent】AI 数字人构建11:FunASR 2:c++工程分析及模型下载
  • 【OpenCV + VS】OpenCV中的图像像素读写