Spring Boot 集成 EasyExcel 的最佳实践:优雅实现 Excel 导入导出

1. 引入

在日常开发中,Excel 的导入和导出几乎是后台系统的标配功能。阿里巴巴的 EasyExcel 是当前主流的 Excel 处理框架,性能优异、使用简单。但在和 Spring Boot 集成时,如果处理不当,容易出现一些典型问题,比如:

  • 导出时报错 Can not close IO

  • 上传导入时文件流未关闭,导致文件句柄泄露

  • 异常响应与二进制流冲突

本文总结了一个通用的 ExcelUtils 工具类,帮助大家在项目中更优雅、安全地完成 Excel 的导入导出。

2. 导出 Excel 到前端

在 Web 项目中,导出功能本质上就是通过 HttpServletResponse 把二进制流写给前端。

关键点

  • 提前设置响应头(Content-TypeContent-Disposition);

  • 不要调用 .autoCloseStream(false) ,避免 Can not close IO 错误;

  • 只负责写出 Excel,不要在失败时再往 response 里写 JSON,避免流冲突。

实现代码

复制代码
    /**
     * 将列表以 Excel 响应给前端
     *
     * @param response  响应
     * @param filename  文件名
     * @param sheetName Excel sheet 名
     * @param head      Excel head 头
     * @param data      数据列表哦
     * @param <T>       泛型,保证 head 和 data 类型的一致性
     * @throws IOException 写入失败的情况
     */
    public static <T> void write(HttpServletResponse response, String filename, String sheetName,
                                 Class<T> head, List<T> data) throws IOException {

        // 提前设置 header
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setHeader("Content-Disposition", "attachment;filename=" + HttpUtils.encodeUtf8(filename));

        // 输出 Excel(去掉 autoCloseStream(false),让 EasyExcel 正常关闭流)
        EasyExcel.write(response.getOutputStream(), head)
                .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())   // 自动列宽
                .registerWriteHandler(new SelectSheetWriteHandler(head))            // 下拉框支持
                .registerConverter(new LongStringConverter())                       // Long 类型精度
                .sheet(sheetName)
                .doWrite(data);
    }

3. 导入 Excel 到后端

Excel 导入功能通常依赖 MultipartFile。这里需要注意:

关键点

  • 判断文件是否为空,提前抛出异常;

  • 使用 try-with-resources 确保输入流始终关闭;

  • 由于我们手动控制流关闭,所以需要 .autoCloseStream(false)

实现代码

复制代码
    /**
     * 从上传的 Excel 文件读取数据
     *
     * @param file 上传的 Excel 文件
     * @param head 解析的实体类
     * @param <T>  泛型,保证 head 和 data 类型一致
     * @return 解析结果列表
     * @throws IOException 读取失败
     */
    public static <T> List<T> read(MultipartFile file, Class<T> head) throws IOException {
        if (file == null || file.isEmpty()) {
            throw exception("导入数据不能为空!");
        }

        // try-with-resources,确保输入流正确关闭
        try (InputStream inputStream = file.getInputStream()) {
            return EasyExcel.read(inputStream, head, null)
                    .autoCloseStream(false) // 不让 EasyExcel 关流,由 try-with-resource 负责
                    .doReadAllSync();
        }
    }

4. ExcelUtils 工具类

复制代码
import cn.iocoder.yudao.framework.common.util.http.HttpUtils;
import cn.iocoder.yudao.framework.excel.core.handler.SelectSheetWriteHandler;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.converters.longconverter.LongStringConverter;
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;

/**
 * Excel 工具类
 *
 */
public class ExcelUtils {

    /**
     * 将列表以 Excel 响应给前端
     *
     * @param response  响应
     * @param filename  文件名
     * @param sheetName Excel sheet 名
     * @param head      Excel head 头
     * @param data      数据列表哦
     * @param <T>       泛型,保证 head 和 data 类型的一致性
     * @throws IOException 写入失败的情况
     */
    public static <T> void write(HttpServletResponse response, String filename, String sheetName,
                                 Class<T> head, List<T> data) throws IOException {

        // 提前设置 header
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setHeader("Content-Disposition", "attachment;filename=" + HttpUtils.encodeUtf8(filename));

        // 输出 Excel(去掉 autoCloseStream(false),让 EasyExcel 正常关闭流)
        EasyExcel.write(response.getOutputStream(), head)
                .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())   // 自动列宽
                .registerWriteHandler(new SelectSheetWriteHandler(head))            // 下拉框支持
                .registerConverter(new LongStringConverter())                       // Long 类型精度
                .sheet(sheetName)
                .doWrite(data);
    }


    /**
     * 从上传的 Excel 文件读取数据
     *
     * @param file 上传的 Excel 文件
     * @param head 解析的实体类
     * @param <T>  泛型,保证 head 和 data 类型一致
     * @return 解析结果列表
     * @throws IOException 读取失败
     */
    public static <T> List<T> read(MultipartFile file, Class<T> head) throws IOException {
        if (file == null || file.isEmpty()) {
            throw exception("导入数据不能为空!");
        }

        // try-with-resources,确保输入流正确关闭
        try (InputStream inputStream = file.getInputStream()) {
            return EasyExcel.read(inputStream, head, null)
                    .autoCloseStream(false) // 不让 EasyExcel 关流,由 try-with-resource 负责
                    .doReadAllSync();
        }
    }
}

5. 常见问题总结

  • 为什么导出不能用 autoCloseStream(false)

    因为 HttpServletResponse.getOutputStream() 是 Servlet 容器管理的流,不该手动干预,EasyExcel 默认关闭即可。

  • 为什么导入建议用 try-with-resources?
    MultipartFile.getInputStream() 不是容器流,如果 EasyExcel 不负责关闭,就需要我们手动关闭,避免内存泄露。

  • 导出失败时如何处理?

    推荐用 response.sendError(500, "导出失败"),而不是再往流里写 JSON。

6. 总结

通过对 ExcelUtils 的封装,我们可以在 Spring Boot 项目中更简洁、优雅地实现 Excel 导入导出,避免常见的流关闭异常和文件句柄泄露问题。

最终方案:

  • 导出 → 不要关闭流,交给 EasyExcel;

  • 导入 → 手动关闭流,避免泄露。

这样,一个通用的 ExcelUtils 工具类就能让团队开发更稳定、更高效。 🚀

相关推荐
StockPP16 小时前
印度尼西亚股票多时间框架K线数据可视化页面
前端·javascript·后端
3***g20516 小时前
如何使用Spring Boot框架整合Redis:超详细案例教程
spring boot·redis·后端
狂奔小菜鸡16 小时前
Day18 | 深入理解Object类
java·后端·java ee
jiayong2316 小时前
Maven NUL文件问题 - 解决方案实施报告
java·maven
未秃头的程序猿16 小时前
🔒 从单机到分布式:三大锁机制深度剖析与实战指南
java·后端
大猫子的技术日记16 小时前
[百题重刷]前缀和 + Hash 表:缓存思想, 消除重复计算
java·缓存·哈希算法
得物技术16 小时前
# 一、项目概览 Dragonboat 是纯 Go 实现的(multi-group)Raft 库。 为应用屏蔽 Raft 复杂性,提供易于使用的 NodeH
后端
4***149016 小时前
Rust系统工具开发实践指南
开发语言·后端·rust
s***353016 小时前
Spring Boot3.x集成Flowable7.x(一)Spring Boot集成与设计、部署、发起、完成简单流程
java·spring boot·后端
大头an16 小时前
Spring Boot CLI 从入门到企业级实战(上下篇)
后端