方案一:封装通用工具类(推荐)
这种方式最灵活,可以在 Service 层或者 Controller 层显式地调用导出逻辑。
1. 编写 ExcelUtil 工具类
这个类主要负责:设置浏览器响应头(防止中文乱码)、创建 Excel Writer 并写入数据。
packagecom.example.demo.util;importcom.alibaba.excel.EasyExcel;importjavax.servlet.http.HttpServletResponse;importjava.io.IOException;importjava.net.URLEncoder;importjava.util.List;publicclassExcelUtil{/** * 通用导出方法 * * @param response HttpServletResponse * @param data 导出的数据集合 * @param head Excel 表头实体类 * @param fileName 文件名(不需要包含 .xlsx) * @param sheetName sheet 名称 */publicstaticvoidexport(HttpServletResponseresponse,List<?>data,Class<?>head,StringfileName,StringsheetName){try{// 1. 设置响应类型response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");response.setCharacterEncoding("utf-8");// 2. 设置文件名(解决中文乱码问题)StringencodedFileName=URLEncoder.encode(fileName,"UTF-8").replaceAll("\\+","%20");response.setHeader("Content-Disposition","attachment;filename*=utf-8''"+encodedFileName+".xlsx");// 3. 写入数据到输出流EasyExcel.write(response.getOutputStream(),head).sheet(sheetName).doWrite(data);}catch(IOExceptione){e.printStackTrace();// 如果出错,重置 response,返回 JSON 错误提示response.reset();response.setContentType("application/json");response.setCharacterEncoding("utf-8");try{response.getWriter().println("{\"code\": 500, \"message\": \"导出失败: "+e.getMessage()+"\"}");}catch(IOExceptionex){ex.printStackTrace();}}}}2. 在 Controller 中调用
在 Controller 方法参数中注入HttpServletResponse,然后调用工具类即可。
@RestController@RequestMapping("/excel")publicclassExcelController{@GetMapping("/manual-export")publicvoiddownload(HttpServletResponseresponse){// 1. 准备数据 (模拟从数据库查询)List<UserExcelVO>list=newArrayList<>();list.add(newUserExcelVO(1L,"张三",20,newDate(),"pwd"));list.add(newUserExcelVO(2L,"李四",25,newDate(),"pwd"));// 2. 调用工具类导出// 注意:这里不需要返回值,因为直接操作了 Response 流ExcelUtil.export(response,list,UserExcelVO.class,"用户报表","Sheet1");}}方案二:直接在 Controller 中写逻辑(适合复杂定制)
如果你需要极其复杂的导出逻辑(例如:动态表头、一个文件包含多个 Sheet、根据参数动态合并单元格等),封装的工具类可能不够用,这时直接写在 Controller 里最合适。
@GetMapping("/complex-export")publicvoidcomplexExport(HttpServletResponseresponse)throwsIOException{// 1. 设置响应头response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");response.setCharacterEncoding("utf-8");StringfileName=URLEncoder.encode("复杂报表","UTF-8");response.setHeader("Content-Disposition","attachment;filename*=utf-8''"+fileName+".xlsx");// 2. 准备数据List<UserExcelVO>userList=getUserData();List<OrderExcelVO>orderList=getOrderData();// 3. 使用 ExcelWriter 实现多 Sheet 导出try(ExcelWriterexcelWriter=EasyExcel.write(response.getOutputStream()).build()){// Sheet 1: 用户信息WriteSheetwriteSheet1=EasyExcel.writerSheet(0,"用户信息").head(UserExcelVO.class)// 使用 User 类的注解作为表头.build();excelWriter.write(userList,writeSheet1);// Sheet 2: 订单信息WriteSheetwriteSheet2=EasyExcel.writerSheet(1,"订单信息").head(OrderExcelVO.class)// 使用 Order 类的注解作为表头.build();excelWriter.write(orderList,writeSheet2);}// try-with-resources 会自动调用 excelWriter.finish()}方案对比
| 方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 注解 (AOP) | 代码极简,业务代码完全不感知导出逻辑。 | 逻辑隐蔽,灵活性较差(难以处理多Sheet、动态表头)。 | 标准化的列表导出。 |
| 工具类 (Util) | 折中方案。代码复用性高,清晰直观。 | 仍需要在 Controller 显式调用。 | 大多数通用业务导出。 |
| 原生写法 | 最灵活,可控制 Excel 的每一个细节。 | 代码冗余,每个接口都要写 Response Header 设置。 | 多 Sheet、动态表头、合并单元格等复杂报表。 |
注意事项:
无论使用哪种非注解方式,关键点都是:Controller 方法的参数要加上HttpServletResponse response,并且方法的返回值建议设为void,因为数据是直接写入 HTTP 输出流的,不需要 Spring MVC 再去处理返回值。