🚀 Excel 导出与导入如何封装为工具类 —— 一名 Java 老程序员的实战分享
作者:一个在业务代码和工具类之间反复横跳的 8 年 Java 工程师
🧠 写在前面
如果你和我一样,在 Java 开发中频繁遇到“导入导出 Excel”的需求,那么你一定也写过这样的代码:
- controller 里 copy paste 一段导出逻辑;
- service 层里嵌套 try-catch 操作 stream;
- 每次导入导出都得重复造轮子;
- 改个模板字段,改得心态爆炸 💥。
So,我决定把这些“业务无关但高频使用”的逻辑封装成一个通用的 Excel 工具类,只做一件事:让导入导出像呼吸一样简单自然。
🧰 技术选型
选择比努力更重要。
我们采用的是当前社区广泛使用的 EasyExcel(阿里开源):
- 🚀 轻量,性能优;
- 🧩 支持注解配置,代码可读性强;
- 🤝 社区活跃,文档完善。
Maven 引入方式如下:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.3.2</version>
</dependency>
📦 工具类封装目标
我们希望的效果是这样的:
- 导出 Excel:
ExcelUtil.export(response, list, "用户信息", UserExportVO.class);
- 导入 Excel:
List<UserImportVO> list = ExcelUtil.importExcel(file, UserImportVO.class);
是不是很丝滑?
🧱 工具类设计思路
我们将工具类分为两块:
- 导出工具类(Export)
- 导入工具类(Import)
同时,支持以下特性:
- 支持设置 Sheet 名、文件名;
- 自动处理响应 Header;
- 支持异常捕捉;
- 支持多 Sheet 导出(可扩展);
- 支持模版填充(进阶)。
📤 导出工具类核心代码
public class ExcelUtil {
/**
* 导出 Excel 文件
*
* @param response HttpServletResponse
* @param list 数据列表
* @param fileName 文件名
* @param clazz 数据类型
* @param <T> 泛型
*/
public static <T> void export(HttpServletResponse response, List<T> list, String fileName, Class<T> clazz) {
try {
// 设置响应头
response.setContentType("application/vnd.ms-excel");
response.setCharacterEncoding("utf-8");
String encodeFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8).replaceAll("\+", "%20");
response.setHeader("Content-disposition", "attachment;filename=" + encodeFileName + ".xlsx");
// 写入数据
EasyExcel.write(response.getOutputStream(), clazz)
.autoCloseStream(true)
.sheet("Sheet1")
.doWrite(list);
} catch (IOException e) {
throw new RuntimeException("Excel 导出失败", e);
}
}
}
📥 导入工具类核心代码
public class ExcelUtil {
/**
* 从 Excel 文件中读取数据
*
* @param file 上传的文件
* @param clazz 数据类型
* @param <T> 泛型
* @return 数据列表
*/
public static <T> List<T> importExcel(MultipartFile file, Class<T> clazz) {
try {
ExcelListener<T> listener = new ExcelListener<>();
EasyExcel.read(file.getInputStream(), clazz, listener).sheet().doRead();
return listener.getData();
} catch (IOException e) {
throw new RuntimeException("Excel 导入失败", e);
}
}
// 内部监听器类
private static class ExcelListener<T> extends AnalysisEventListener<T> {
private final List<T> data = new ArrayList<>();
@Override
public void invoke(T t, AnalysisContext context) {
data.add(t);
}
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
// no-op
}
public List<T> getData() {
return data;
}
}
}
✅ 使用示例
导出 Controller 示例
@GetMapping("/export")
public void exportUser(HttpServletResponse response) {
List<UserExportVO> list = userService.getAllUsers();
ExcelUtil.export(response, list, "用户列表", UserExportVO.class);
}
导入 Controller 示例
@PostMapping("/import")
public String importUser(@RequestParam MultipartFile file) {
List<UserImportVO> list = ExcelUtil.importExcel(file, UserImportVO.class);
userService.saveBatch(list);
return "导入成功,共 " + list.size() + " 条数据";
}
🔍 实体类注解说明
EasyExcel 使用注解来标注 Excel 字段:
@Data
public class UserExportVO {
@ExcelProperty("用户ID")
private Long id;
@ExcelProperty("用户名")
private String name;
@ExcelProperty("邮箱")
private String email;
}
🧩 拓展建议
- ✅ 支持多 Sheet 导出;
- ✅ 支持模板导出(模板 + 数据填充);
- ✅ 支持导入校验(如手机号格式、必填字段);
- ✅ 支持异常数据行记录;
- ✅ 封装为 Spring Boot Starter,提升复用度。
🧘 总结
写工具类的终极目标,是让业务代码更 专注业务。
封装一个高复用的 Excel 工具类,不但提升了开发效率,还减少了重复劳动。作为一个有 8 年经验的 Java 程序员,我越来越意识到:
优雅,往往来自于对细节的极致追求。