如何在REST Controller中动态下载Excel文件(不依赖HttpResponse)
问题描述
我们正将基于会话的应用迁移至REST API。当前应用通过将生成的Excel工作簿写入HttpResponse的OutputStream来动态下载小型Excel文件,现有实现代码如下,请问如何在不使用HttpResponse对象、不保存文件至临时路径的REST API中实现该动态下载功能?
现有实现代码
@RequestMapping(value = "exportRules", method = GET) public void exportRuleResults(final HttpServletRequest request, final HttpServletResponse response) { try { final SXSSFWorkbook workbook = new SXSSFWorkbook(100); // summary report final Sheet sheet = workbook.createSheet("Rules Details"); //Headers (First row of report) final int rows = writeRulesHeaders(workbook, sheet); int pageNumber = 0; final ExportReturnVals ret = new ExportReturnVals(1, true); ret.rowIndex = rows; ret.totalnumberOfRecords = 1; while (ret.hasMoreRows) { writeRows(pageNumber, sheet, ret, workbook); pageNumber++; } OutputStream os=getOutputStream(response, ret); workbook.write(os); response.flushBuffer(); workbook.dispose(); os.close(); } catch (final IOException e) { throw new RuntimeException(e.getMessage(), e); } } private OutputStream getOutputStream(final HttpServletResponse response, final ExcelExportReturnVals ret) throws IOException { final OutputStream os = response.getOutputStream(); OutputStream outputStream; if (ret.totalnumberOfRecords > MAX_RECORD_NONZIP) { response.setContentType("application/zip"); response.addHeader("Content-Disposition", "attachment; filename=\"" + "RuleDetails" + ".zip\""); outputStream = new ZipOutputStream(new BufferedOutputStream(os)); ((ZipOutputStream) outputStream).putNextEntry(new ZipEntry("RuleDetails" + ".xlsx")); }else { response.setContentType("application/xlsx"); response.addHeader("Content-Disposition", "attachment; filename=\"" + "RuleDetails" + ".xlsx\""); outputStream = new BufferedOutputStream(os); } return outputStream; }
解决方案
在Spring REST API中,可通过返回ResponseEntity<Resource>实现无临时文件、不依赖HttpResponse的动态下载。核心逻辑是将Excel/压缩包内容写入内存流,再封装为资源对象配合响应头完成下载。
改造步骤
- 替换HttpResponse输出流为
ByteArrayOutputStream,在内存中暂存文件内容 - 保留原有的记录数阈值判断,分别处理普通Excel和压缩包场景
- 将内存中的字节数据封装为
ByteArrayResource,搭配正确的响应头返回
改造后代码示例
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.core.io.ByteArrayResource; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import java.io.ByteArrayOutputStream; import java.io.BufferedOutputStream; import java.util.zip.ZipOutputStream; import java.util.zip.ZipEntry; import org.apache.poi.xssf.streaming.SXSSFWorkbook; import org.apache.poi.ss.usermodel.Sheet; @RequestMapping(value = "exportRules") @GetMapping public ResponseEntity<ByteArrayResource> exportRuleResults() { ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); SXSSFWorkbook workbook = null; try { workbook = new SXSSFWorkbook(100); Sheet sheet = workbook.createSheet("Rules Details"); // 写入表头 int rows = writeRulesHeaders(workbook, sheet); int pageNumber = 0; ExportReturnVals ret = new ExportReturnVals(1, true); ret.rowIndex = rows; ret.totalnumberOfRecords = 1; // 写入数据行 while (ret.hasMoreRows) { writeRows(pageNumber, sheet, ret, workbook); pageNumber++; } HttpHeaders headers = new HttpHeaders(); if (ret.totalnumberOfRecords > MAX_RECORD_NONZIP) { // 处理压缩包场景 headers.setContentType(MediaType.APPLICATION_OCTET_STREAM); headers.setContentDispositionFormData("attachment", "RuleDetails.zip"); try (ZipOutputStream zipOut = new ZipOutputStream(new BufferedOutputStream(outputStream))) { zipOut.putNextEntry(new ZipEntry("RuleDetails.xlsx")); workbook.write(zipOut); zipOut.closeEntry(); } } else { // 处理普通Excel场景(使用标准MIME类型) headers.setContentType(MediaType.parseMediaType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")); headers.setContentDispositionFormData("attachment", "RuleDetails.xlsx"); workbook.write(outputStream); } workbook.dispose(); ByteArrayResource resource = new ByteArrayResource(outputStream.toByteArray()); return new ResponseEntity<>(resource, headers, HttpStatus.OK); } catch (Exception e) { throw new RuntimeException("导出Excel失败", e); } finally { try { if (workbook != null) workbook.close(); outputStream.close(); } catch (Exception ex) { // 忽略资源关闭异常 } } }
关键说明
- 内存流暂存:用
ByteArrayOutputStream替代HttpResponse输出流,全程在内存中处理文件内容,无需临时文件 - 资源封装:
ByteArrayResource是Spring提供的资源类,可直接适配ResponseEntity的返回要求 - 标准响应头:
- 普通Excel使用标准MIME类型
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,替代原代码的非标准application/xlsx - 通过
setContentDispositionFormData设置下载文件名,自动处理编码问题
- 普通Excel使用标准MIME类型
- 资源释放:通过
try-with-resources和finally块确保工作簿、流资源被正确关闭,避免内存泄漏
内容的提问来源于stack exchange,提问作者Ram
相关产品推荐
相关产品推荐

