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 工具类就能让团队开发更稳定、更高效。 🚀

相关推荐
曹牧38 分钟前
文档格式:OFD
java
豆角焖肉1 小时前
Maven进阶与搭建私服
java·maven
大不点wow1 小时前
Java序列化与反序列化:让对象走出JVM
java·开发语言·jvm
止语Lab1 小时前
好的 DX 不等于少写代码——三种语言的摩擦力设计课
后端
吃饱了得干活1 小时前
别再手动解析 LLM 输出了!LangChain 四种结构化输出方案对比
后端·python·langchain
噢,我明白了1 小时前
Java中日期和字符串的处理
java·开发语言·日期
程序员天天困1 小时前
Arthas trace 命令怎么用?一行定位最慢那行代码
jvm·后端
Huiturn1 小时前
GPT 5.6 连续编码 10 小时,纯 Python 啃下 Word 二进制格式——doc2docx 实现拆解
后端
dkbnull1 小时前
Spring Boot请求处理组件对比详解
java·spring boot
jun_bai1 小时前
Orthanc服务器使用java上传dicom影像文件
java·运维·服务器