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

1. 引入

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

  • 导出时报错 Can not close IO;

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

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

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

2. 导出 Excel 到前端

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

关键点

  • 提前设置响应头(Content-Type 和 Content-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 工具类就能让团队开发更稳定、更高效。 🚀

相关推荐
m0_5873830038 分钟前
广州 24 小时自助健身房解决方案技术实现与实战指南
java·spring·小程序·架构·需求分析
·云扬·1 小时前
Prompt 工程与安全:从 System 设计到注入防御
java·人工智能·安全·ai·prompt
代码不停2 小时前
自动化测试(java)
java·开发语言·selenium·自动化
行百里er2 小时前
Redis 完结篇——Redis 热点问题在 Spring Boot 项目中实战
redis·后端
高频因子挖掘机3 小时前
只取最近 60 个交易日,却每次拉全历史:量化脚本变慢的原因与改法
后端·github·api
鬼手点金4 小时前
opencode-rules 插件
java·开发语言·前端·人工智能·chrome·git·github
孟健4 小时前
从5000亿Token到83%字节匹配:AI逆向工程与重构的工程闭环实践
后端·架构·ai编程
paopaokaka_luck5 小时前
滑雪场管理系统(AI智能助手流式对话,ECharts 运营数据分析、教练、雪具与场地一体化预约,钱包支付退款、订单核销与服务评价)
java·人工智能·spring boot·网络协议·数据分析·echarts
SL_staff5 小时前
JVS混合架构实践:能力引擎+动态模型如何支撑企业级可持续交付
spring boot·低代码·全栈
码事漫谈5 小时前
丢得静悄悄,谈一谈 Agent 的记忆是怎么没的
后端