spring文件下载的四种方式及对比分析
作者:言不由衷煦
本文对比spring文件下载的四种方式,每种方式结合实例代码给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧
方式一:通过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();
}
}
/**
* 根据浏览器类型编码文件名
* @param request 请求对象
* @param fileName 原始文件名
* @return 编码后的文件名
*/
private static String encodeFileName(HttpServletRequest request, String fileName) {
String userAgent = request.getHeader("User-Agent");
// 处理IE及Edge浏览器
if (userAgent.contains("MSIE") || userAgent.contains("Trident") || userAgent.contains("Edge")) {
return cn.hutool.core.net.URLEncoder.createDefault().encode(fileName, StandardCharsets.UTF_8);
}
// 其他浏览器使用ISO-8859-1编码
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()))
// 省略 encodeFileName 方法
.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.setContentLength() 则下载的文件只能是 50 多兆
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;
/**
* description
*
* @author lijin
* @since 2025-08-12 14:05
*/
@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();
}
}
/**
* 根据浏览器类型编码文件名
* @param request 请求对象
* @param fileName 原始文件名
* @return 编码后的文件名
*/
private static String encodeFileName(HttpServletRequest request, String fileName) {
String userAgent = request.getHeader("User-Agent");
// 处理IE及Edge浏览器
if (userAgent.contains("MSIE") || userAgent.contains("Trident") || userAgent.contains("Edge")) {
return cn.hutool.core.net.URLEncoder.createDefault().encode(fileName, StandardCharsets.UTF_8);
}
// 其他浏览器使用ISO-8859-1编码
return new String(fileName.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1);
}
}到此这篇关于spring文件下载的四种方式及对比分析的文章就介绍到这了,更多相关spring文件下载内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
