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