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);}
}