spring文件下载的四种方式及对比分析

 更新时间:2025年08月13日 09:40:31   作者:言不由衷煦  
本文对比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文件下载内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 基于Spring的RPC通讯模型的使用与比较

    基于Spring的RPC通讯模型的使用与比较

    这篇文章主要介绍了基于Spring的RPC通讯模型的使用与比较,详细的介绍了RMI、Caucho的Hessian和Burlap以及Spring自带的HTTP invoker,感兴趣的可以了解一下
    2018-09-09
  • Java如何获取Date的“昨天”与“明天”示例代码

    Java如何获取Date的“昨天”与“明天”示例代码

    最近在做项目的时候用到Date和Calendar比较多,而且用到的方式也比较全,突然想到一个问题,Java如何获取Date的"昨天"与"明天",也就是前一天和后一天呢?思考后写出了方法,想着万一以后用到,就总结出来,也方便有需要的朋友们参考借鉴,下面来一起看看吧。
    2016-12-12
  • SpringBoot+Redis实现数据字典的方法

    SpringBoot+Redis实现数据字典的方法

    这篇文章主要介绍了SpringBoot+Redis实现数据字典的方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-10-10
  • IDEA修改生成jar包名字的两种方法实现

    IDEA修改生成jar包名字的两种方法实现

    本文主要介绍了IDEA修改生成jar包名字的两种方法实现,通过简单的步骤,您可以修改项目名称并在打包时使用新的名称,具有一定的参考价值,感兴趣的可以了解下
    2023-08-08
  • 使用idea将工具类打包使用的详细教程

    使用idea将工具类打包使用的详细教程

    这篇文章主要介绍了使用idea将工具类打包使用的详细教程,本文通过图文并茂给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-03-03
  • 简单了解JAVA内存区域效果知识

    简单了解JAVA内存区域效果知识

    这篇文章主要介绍了简单了解JAVA内存区域效果知识,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-10-10
  • SpringDataJpa的@Query注解报错的解决

    SpringDataJpa的@Query注解报错的解决

    这篇文章主要介绍了SpringDataJpa的@Query注解报错的解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-12-12
  • springboot bean循环依赖实现以及源码分析

    springboot bean循环依赖实现以及源码分析

    最近在使用Springboot做项目的时候,遇到了一个循环依赖的 问题,所以下面这篇文章主要给大家介绍了关于springboot bean循环依赖实现以及源码分析的相关资料,需要的朋友可以参考下
    2021-06-06
  • 基于SpringCloudGateway实现微服务网关的方式

    基于SpringCloudGateway实现微服务网关的方式

    Spring Cloud Gateway是Spring 官方基于Spring 5.0,Spring Boot 2.0和Project Reactor 等技术开发的网关,旨在为微服务架构提供一种简单而有效的统一的API路由管理方式,对SpringCloudGateway实现微服务网关相关知识感兴趣的朋友一起看看吧
    2021-12-12
  • spring boot2.0总结介绍

    spring boot2.0总结介绍

    今天小编就为大家分享一篇关于spring boot2.0总结介绍,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2018-12-12

最新评论