spring文件下載的方式
- 方式一:通過ResponseEntity<Resource> 方式來下載
- 方式二:通過ResponseEntity<StreamingResponseBody> 方式來下載
- 方式三:通過Servlet原生下載
- 方式四:通過ResponseEntity<byte[]> 方式來下載
- 四種下載方式的對比
- 完整的代碼
方式一:通過ResponseEntity 方式來下載
@ApiOperation(value = "下載數據文檔模板")
@GetMapping("/download-template/1")
public ResponseEntity<Resource> downloadFile01(HttpServletRequest request) {try {Resource resource = new ClassPathResource("templates/ubuntu-20.04.6-desktop-amd64.iso");if (resource.exists() || resource.isReadable()) {return ResponseEntity.ok().header(HttpHeaders.CONTENT_LENGTH, String.valueOf(resource.contentLength())).header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + encodeFileName(request, resource.getFilename())).contentType(MediaTypeFactory.getMediaType(resource).orElse(MediaType.APPLICATION_OCTET_STREAM)).body(resource);} else {return ResponseEntity.notFound().build();}} catch (Exception e) {log.error("文件模板下載失敗!", e);return ResponseEntity.internalServerError().build();}
}
private static String encodeFileName(HttpServletRequest request, String fileName) {String userAgent = request.getHeader("User-Agent");if (userAgent.contains("MSIE") || userAgent.contains("Trident") || userAgent.contains("Edge")) {return cn.hutool.core.net.URLEncoder.createDefault().encode(fileName, StandardCharsets.UTF_8);}return new String(fileName.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1);
}
方式二:通過ResponseEntity 方式來下載
@ApiOperation(value = "下載數據文檔模板")
@GetMapping("/download-template/2")
public ResponseEntity<StreamingResponseBody> downloadFile02(HttpServletRequest request) {try {Resource resource = new ClassPathResource("templates/ubuntu-20.04.6-desktop-amd64.iso");StreamingResponseBody body = outputStream -> {FileUtil.writeToStream(resource.getFile(), outputStream);};if (resource.exists() || resource.isReadable()) {return ResponseEntity.ok().header(HttpHeaders.CONTENT_LENGTH, String.valueOf(resource.contentLength())).header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + encodeFileName(request, resource.getFilename())).contentType(MediaTypeFactory.getMediaType(resource).orElse(MediaType.APPLICATION_OCTET_STREAM)).body(body);} else {return ResponseEntity.notFound().build();}} catch (Exception e) {log.error("文件模板下載失敗!", e);return ResponseEntity.internalServerError().build();}
}
方式三:通過Servlet原生下載
@ApiOperation(value = "下載數據文檔模板")
@GetMapping("/download-template/3")
public void downloadFile03(HttpServletRequest request, HttpServletResponse response) throws IOException {Resource resource = new ClassPathResource("templates/ubuntu-20.04.6-desktop-amd64.iso");response.setContentType(MediaTypeFactory.getMediaType(resource).orElse(MediaType.APPLICATION_OCTET_STREAM).toString());response.setHeader("Content-Disposition", "attachment; filename=" + encodeFileName(request, resource.getFilename()));response.setContentLengthLong(resource.contentLength());FileUtil.writeToStream(resource.getFile(), response.getOutputStream());
}
方式四:通過ResponseEntity<byte[]> 方式來下載
@ApiOperation(value = "下載數據文檔模板")
@GetMapping("/download-template/4")
public ResponseEntity<byte[]> downloadFile04(HttpServletRequest request, HttpServletResponse response) throws IOException {try {Resource resource = new ClassPathResource("templates/ubuntu-20.04.6-desktop-amd64.iso");if (resource.exists() || resource.isReadable()) {return ResponseEntity.ok().header(HttpHeaders.CONTENT_LENGTH, String.valueOf(resource.contentLength())).header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + encodeFileName(request, resource.getFilename())).contentType(MediaTypeFactory.getMediaType(resource).orElse(MediaType.APPLICATION_OCTET_STREAM)).body(FileUtil.readBytes(resource.getFile()));} else {return ResponseEntity.notFound().build();}} catch (Exception e) {log.error("文件模板下載失敗!", e);return ResponseEntity.internalServerError().build();}
}
四種下載方式的對比
1、核心特性對比
方式? | ?內存占用? | ?適用場景? | ?流式支持? | ?資源管理? | ?代碼復雜度? |
---|
ResponseEntity<byte[]> | 高(全量加載) | 小文件(<10MB)如配置文件、圖片 | ? | 自動釋放內存 | 低(簡單封裝) |
ResponseEntity<Resource> | 中(按需加載) | 動態資源(如JAR內文件、數據庫Blob) | ?(需手動關閉流) | 需顯式關閉InputStream | 中(需處理流) |
ResponseEntity<StreamingResponseBody> | 極低(分塊傳輸) | 超大文件(視頻、日志等) | ?(自動分塊) | 自動處理流生命周期 | 高(需分塊邏輯) |
Servlet原生下載 | 低(手動控制) | 需要精細控制響應頭/流的場景 | ?(手動實現) | 需手動關閉流 | 高(冗余代碼) |
2、典型場景推薦
- ?快速小文件下載?:優先使用
ResponseEntity<byte[]>
,直接返回字節數組,無需流控制。 - ?動態資源或加密文件?:選擇
ResponseEntity<Resource>
,靈活處理輸入流。 - ?**超大文件(GB級)**?:必須用
StreamingResponseBody
分塊傳輸,避免OOM。 - ?兼容舊系統或特殊需求?:
Servlet原生下載
(如需要自定義響應頭邏輯)
完整的代碼
import cn.hutool.core.io.FileUtil;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.MediaTypeFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
@Slf4j
@Controller
@RequestMapping("/cycle-data/document01")
public class DownloadController {@ApiOperation(value = "下載數據文檔模板")@GetMapping("/download-template/1")public ResponseEntity<Resource> downloadFile01(HttpServletRequest request) {try {Resource resource = new ClassPathResource("templates/ubuntu-20.04.6-desktop-amd64.iso");if (resource.exists() || resource.isReadable()) {return ResponseEntity.ok().header(HttpHeaders.CONTENT_LENGTH, String.valueOf(resource.contentLength())).header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + encodeFileName(request, resource.getFilename())).contentType(MediaTypeFactory.getMediaType(resource).orElse(MediaType.APPLICATION_OCTET_STREAM)).body(resource);} else {return ResponseEntity.notFound().build();}} catch (Exception e) {log.error("文件模板下載失敗!", e);return ResponseEntity.internalServerError().build();}}@ApiOperation(value = "下載數據文檔模板")@GetMapping("/download-template/2")public ResponseEntity<StreamingResponseBody> downloadFile02(HttpServletRequest request) {try {Resource resource = new ClassPathResource("templates/ubuntu-20.04.6-desktop-amd64.iso");StreamingResponseBody body = outputStream -> {FileUtil.writeToStream(resource.getFile(), outputStream);};if (resource.exists() || resource.isReadable()) {return ResponseEntity.ok().header(HttpHeaders.CONTENT_LENGTH, String.valueOf(resource.contentLength())).header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + encodeFileName(request, resource.getFilename())).contentType(MediaTypeFactory.getMediaType(resource).orElse(MediaType.APPLICATION_OCTET_STREAM)).body(body);} else {return ResponseEntity.notFound().build();}} catch (Exception e) {log.error("文件模板下載失敗!", e);return ResponseEntity.internalServerError().build();}}@ApiOperation(value = "下載數據文檔模板")@GetMapping("/download-template/3")public void downloadFile03(HttpServletRequest request, HttpServletResponse response) throws IOException {Resource resource = new ClassPathResource("templates/ubuntu-20.04.6-desktop-amd64.iso");response.setContentType(MediaTypeFactory.getMediaType(resource).orElse(MediaType.APPLICATION_OCTET_STREAM).toString());response.setHeader("Content-Disposition", "attachment; filename=" + encodeFileName(request, resource.getFilename()));response.setContentLengthLong(resource.contentLength());FileUtil.writeToStream(resource.getFile(), response.getOutputStream());}@ApiOperation(value = "下載數據文檔模板")@GetMapping("/download-template/4")public ResponseEntity<byte[]> downloadFile04(HttpServletRequest request, HttpServletResponse response) throws IOException {try {Resource resource = new ClassPathResource("templates/ubuntu-20.04.6-desktop-amd64.iso");if (resource.exists() || resource.isReadable()) {return ResponseEntity.ok().header(HttpHeaders.CONTENT_LENGTH, String.valueOf(resource.contentLength())).header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + encodeFileName(request, resource.getFilename())).contentType(MediaTypeFactory.getMediaType(resource).orElse(MediaType.APPLICATION_OCTET_STREAM)).body(FileUtil.readBytes(resource.getFile()));} else {return ResponseEntity.notFound().build();}} catch (Exception e) {log.error("文件模板下載失敗!", e);return ResponseEntity.internalServerError().build();}}private static String encodeFileName(HttpServletRequest request, String fileName) {String userAgent = request.getHeader("User-Agent");if (userAgent.contains("MSIE") || userAgent.contains("Trident") || userAgent.contains("Edge")) {return cn.hutool.core.net.URLEncoder.createDefault().encode(fileName, StandardCharsets.UTF_8);}return new String(fileName.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1);}
}