Java使用EasyExcel导出Excel的全流程详解

 更新时间:2025年11月13日 08:41:46   作者:代码怪兽大大作战  
在 Java 后端开发中,Excel 导出是非常常见的需求,涉及报表、数据分析或业务导出,本文将结合 阿里 EasyExcel 库,详细讲解 Excel 导出流程,包括工具类设计、样式、列宽策略、对象映射、输出流处理,需要的朋友可以参考下

引言

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

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

一、什么是 EasyExcel

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

高性能

  • 基于 SAX 流式解析和写入,内存占用低
  • 可以轻松处理百万行数据导出,不会 OOM

易用

  • 注解式对象映射(@ExcelProperty
  • 只需定义对象类即可生成 Excel 表格

样式灵活

  • 表头样式、内容样式、列宽、合并单元格等都可自定义

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);

            // 创建 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 简单导出方法

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效果

以上就是Java使用EasyExcel导出Excel的全流程详解的详细内容,更多关于Java EasyExcel导出Excel的资料请关注脚本之家其它相关文章!

相关文章

  • IDEA2021没有Web Application的解决过程及分析

    IDEA2021没有Web Application的解决过程及分析

    在IDEA中创建WebApplication项目时,若AddFrameworksSupport无该选项,可先创建普通Java项目后添加框架支持,或通过快捷键打开registry,勾选javaee.legacy.project.wizard以启用,如解决您的问题,欢迎点赞支持
    2025-09-09
  • mongo分布式锁Java实现方法(推荐)

    mongo分布式锁Java实现方法(推荐)

    下面小编就为大家带来一篇mongo分布式锁Java实现方法(推荐)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-07-07
  • Spring Dao层@Repository与@Mapper的使用

    Spring Dao层@Repository与@Mapper的使用

    这篇文章主要介绍了Spring Dao层@Repository与@Mapper的使用方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-02-02
  • sqlserver的jdbc配置方法

    sqlserver的jdbc配置方法

    这篇文章主要介绍了sqlserver的jdbc配置方法,需要的朋友可以参考下
    2014-04-04
  • Java实现LeetCode(54.螺旋矩阵)

    Java实现LeetCode(54.螺旋矩阵)

    这篇文章主要介绍了Java实现LeetCode(螺旋矩阵),本文列出题目和写题的思路。给出完整的解法代码,需要的朋友可以参考下
    2021-06-06
  • Java实现数据库图片上传功能详解

    Java实现数据库图片上传功能详解

    这篇文章主要为大家详细介绍了如何使用Java实现数据库图片上传功能,包含从数据库拿图片传递前端渲染,感兴趣的小伙伴可以跟随小编一起学习一下
    2025-03-03
  • Kotlin与Java 泛型缺陷和应用场景详解

    Kotlin与Java 泛型缺陷和应用场景详解

    这篇文章主要为大家介绍了Kotlin与Java 泛型缺陷和应用场景详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-12-12
  • Java微信扫码登录功能并实现认证授权全过程

    Java微信扫码登录功能并实现认证授权全过程

    这篇文章主要给大家介绍了关于Java微信扫码登录功能并实现认证授权的相关资料,要在Java中实现微信扫码登录,您可以按照以下步骤进行操作,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2023-10-10
  • Java中遍历数组使用foreach循环还是for循环?

    Java中遍历数组使用foreach循环还是for循环?

    这篇文章主要介绍了Java中遍历数组使用foreach循环还是for循环?本文着重讲解for语句的语法并给出使用实例,同时总结出尽量使用foreach语句遍历数组,需要的朋友可以参考下
    2015-06-06
  • mybatisplus的逻辑删除过程

    mybatisplus的逻辑删除过程

    这篇文章主要介绍了mybatisplus的逻辑删除过程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2025-08-08

最新评论