---
URL: "/zh-CN/blog/2026-04/SpringBoot-Download.html"
LLMS_URL: "/zh-CN/blog/2026-04/SpringBoot-Download.md"
lastUpdated: true
commentabled: true
recommended: true
title: "SpringBoot文件下载功能完整实现指南"
description: "SpringBoot文件下载功能完整实现指南"
date:

pageClass: "blog-page-class"
cover: "/covers/springboot.svg"
---

---
lastUpdated: true
commentabled: true
recommended: true
title: SpringBoot文件下载功能完整实现指南
description: SpringBoot文件下载功能完整实现指南
date: 2026-04-13 09:45:00 
pageClass: blog-page-class
cover: /covers/springboot.svg
---

## 一、 Spring Boot文件下载简介 ##

文件下载是Web应用程序中常见的功能需求，尤其在需要向用户提供生成的报告、文档或用户上传的文件时尤为重要。Spring Boot 框架凭借其简洁的配置和强大的功能，为实现文件下载提供了多种灵活高效的解决方案。

通过合理的代码设计，开发者可以轻松实现单文件下载、多文件打包下载等复杂场景，同时保证良好的用户体验和系统安全性。 在Spring Boot中实现文件下载功能主要依赖于Spring MVC框架的基础设施，通过控制器方法处理HTTP请求，将文件数据以流的形式写入HTTP响应中。正确的实现方式不仅要考虑功能完整性，还需要关注性能优化、异常处理和安全性等关键因素。

本文将系统介绍Spring Boot中文件下载的各种实现方式，从基础入门到高级应用，帮助开发者掌握这一核心技能。 传统的文件下载功能在Servlet体系中需要开发者手动处理HTTP响应头设置、流拷贝和资源释放等底层细节，而Spring Boot通过封装这些通用操作，提供了更简洁的API接口。这使得开发者可以更专注于业务逻辑实现，而无需重复编写样板代码。无论是简单的单文件下载，还是复杂的多文件打包下载，Spring Boot都提供了相应的解决方案。

## 二、 基础实现方法 ##

### 使用ResponseEntity实现 ###

ResponseEntity是Spring框架中一个强大的类，它代表整个HTTP响应，包括状态码、头部信息和响应体。使用`ResponseEntity`实现文件下载的优势在于可以精确控制响应的各个方面，同时代码简洁易懂。下面是使用`ResponseEntity`实现文件下载的基本步骤。 首先，需要在控制器类中创建一个处理下载请求的方法。该方法应返回`ResponseEntity`类型，其中`Resource`是Spring框架中用于抽象各种来源资源的接口。通过`FileSystemResource`可以方便地表示文件系统上的一个具体文件。

```java
import org.springframework.core.io.FileSystemResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class FileDownloadController {
    
    private static final String FILE_DIRECTORY = "/path/to/your/files/directory/";
    
    @GetMapping("/download/{fileName:.+}")
    public ResponseEntity<FileSystemResource> downloadFile(@PathVariable String fileName) {
        // 构建文件路径
        String filePath = FILE_DIRECTORY + fileName;
        FileSystemResource file = new FileSystemResource(filePath);
        
        // 检查文件是否存在
        if (!file.exists()) {
            throw new RuntimeException("文件未找到");
        }
        
        // 设置响应头
        HttpHeaders headers = new HttpHeaders();
        headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename="" + fileName + """);
        
        return ResponseEntity.ok()
                .headers(headers)
                .contentType(MediaType.APPLICATION_OCTET_STREAM)
                .body(file);
    }
}
```

在这个示例中，`@GetMapping`注解将HTTP GET请求映射到`/download/{fileName}`路径，其中`{fileName}`是路径变量，表示要下载的文件名。`produces = MediaType.APPLICATION_OCTET_STREAM_VALUE`确保响应内容类型为二进制流，这是文件下载的通用类型。

使用`ResponseEntity`的优点是代码简洁且符合RESTful设计风格。通过`ResponseEntity`的链式调用，可以直观地设置响应状态、头部信息和响应体。此外，Spring框架会自动处理资源释放等底层细节，减少内存泄漏的风险。

### 使用HttpServletResponse实现 ###

另一种常见的文件下载实现方式是直接使用`HttpServletResponse`对象。这种方法更为底层，提供了对HTTP响应的精细控制，适用于需要特殊处理的场景。与`ResponseEntity`方式相比，`HttpServletResponse`方式需要开发者手动设置响应头和实现流拷贝逻辑。

以下是使用`HttpServletResponse`实现文件下载的示例代码：

```java
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletResponse;
import java.io.*;

@RestController
public class FileDownloadController {
    
    @GetMapping("/download")
    public void fileDownload(HttpServletResponse response) {
        // 文件路径
        String filePath = "D:\文件下载测试\实验6打印.docx";
        // 文件名
        String fileName = "实验6.docx";
        File file = new File(filePath);
        
        // 设置响应头
        response.setHeader("Content-Type", "application/octet-stream");
        // 解决中文文件名乱码问题
        response.setHeader("Content-Disposition", "attachment;filename=" + 
                URLEncoder.encode(fileName, StandardCharsets.UTF_8));
        
        // 创建流
        FileInputStream is = null;
        BufferedInputStream bs = null;
        ServletOutputStream os = null;
        
        // 流拷贝到客户端
        try {
            is = new FileInputStream(file);
            bs = new BufferedInputStream(is);
            os = response.getOutputStream();
            
            byte[] buffer = new byte[1024];
            int len = 0;
            while ((len = bs.read(buffer)) != -1) {
                os.write(buffer, 0, len);
            }
        } catch (IOException ex) {
            ex.printStackTrace();
        } finally {
            // 关闭流
            try {
                if (is != null) is.close();
                if (bs != null) bs.close();
                if (os != null) {
                    os.flush();
                    os.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
```

这种方法需要手动管理流的生命周期，包括创建、使用和关闭。代码中使用了缓冲区`（BufferedInputStream）`来提高读取效率，并通过循环读取-写入的方式将文件内容传输到客户端。需要注意的是，必须确保在`finally`块中正确关闭所有流资源，避免资源泄漏。

使用`HttpServletResponse`的主要优势在于对底层细节的完全控制，例如可以实现下载进度监控、特殊错误处理等高级功能。然而，这种方式的代码量较大，需要开发者自行处理更多细节，对于简单下载场景可能显得过于复杂。

### 使用InputStreamResource和FileSystemResource ###

Spring框架提供了多种`Resource`实现类，用于抽象不同来源的资源。除了上述两种方法外，还可以使用`InputStreamResource`或直接使用`FileSystemResource`实现文件下载。这些方法在实现细节上有所不同，但核心思想相似。

以下是使用`InputStreamResource`的示例：

```java
@GetMapping("/download")
public ResponseEntity<InputStreamResource> downloadFile() throws IOException {
    String filePath = "path/to/your/file.txt";
    File file = new File(filePath);
    FileInputStream fileInputStream = new FileInputStream(file);
    
    HttpHeaders headers = new HttpHeaders();
    headers.add("Content-Disposition", "attachment; filename=" + file.getName());
    headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
    
    return ResponseEntity.ok()
            .headers(headers)
            .contentLength(file.length())
            .body(new InputStreamResource(fileInputStream));
}
```

使用`InputStreamResource`时需要注意，Spring框架不会自动关闭输入流，因此需要确保流被正确关闭。一种改进方法是使用`try-with-resources`语句管理流资源，但这样会导致Resource对象在方法返回后无法使用。因此，这种方法适用于能够确保资源正确释放的场景。

相比之下，`FileSystemResource`更为简单，因为它是基于文件路径创建的，Spring会处理资源的加载和释放：

```java
@GetMapping("/download")
public ResponseEntity<Resource> downloadFile() {
    File file = new File("/path/to/file");
    Resource resource = new FileSystemResource(file);
    
    return ResponseEntity.ok()
            .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + file.getName())
            .contentType(MediaType.APPLICATION_OCTET_STREAM)
            .contentLength(file.length())
            .body(resource);
}
```

这种方法结合了`ResponseEntity`的简洁性和`Resource`的抽象能力，是Spring Boot中最推荐使用的文件下载方式之一。

## 三、 高级功能与技巧 ##

### 多文件打包下载 ###

在实际应用中，经常需要将多个文件打包成一个压缩文件供用户下载。这种需求在文档管理系统、数据导出功能中尤为常见。Spring Boot结合Java的`ZipOutputStream`类可以轻松实现多文件打包下载功能。

以下是多文件打包下载的完整示例：

```java
@GetMapping("/download/package")
public void compressedDownload(HttpServletResponse response) {
    // 文件名数组
    String[] names = {"10.png", "实验报告.docx"};
    // 文件路径数组
    String[] paths = {"D:\文件下载测试\6z10.png", "D:\文件下载测试\实验6打印.docx"};
    
    // 创建临时目录存放压缩包
    String directory = "D:\文件压缩包临时目录";
    File directoryFile = new File(directory);
    if (!directoryFile.exists()) {
        directoryFile.mkdirs();
    }
    
    // 设置压缩包文件名（使用时间戳避免重名）
    SimpleDateFormat formatter = new SimpleDateFormat("yyyy年MM月dd日HH时mm分ss秒");
    String zipFileName = formatter.format(new Date()) + ".zip";
    String strZipPath = directory + "\" + zipFileName;
    
    // 创建ZipOutputStream
    ZipOutputStream zipStream = null;
    FileInputStream zipSource = null;
    BufferedInputStream bufferStream = null;
    File zipFile = new File(strZipPath);
    
    try {
        zipStream = new ZipOutputStream(new FileOutputStream(zipFile));
        
        for (int i = 0; i < paths.length; i++) {
            String realFileName = names[i];
            String realFilePath = paths[i];
            File file = new File(realFilePath);
            
            if (file.exists()) {
                zipSource = new FileInputStream(file);
                // 创建压缩条目
                ZipEntry zipEntry = new ZipEntry(realFileName);
                zipStream.putNextEntry(zipEntry);
                
                // 写入文件到压缩包
                bufferStream = new BufferedInputStream(zipSource, 1024 * 10);
                int read = 0;
                byte[] buf = new byte[1024 * 10];
                while ((read = bufferStream.read(buf, 0, 1024 * 10)) != -1) {
                    zipStream.write(buf, 0, read);
                }
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        // 关闭流
        try {
            if (null != bufferStream) bufferStream.close();
            if (null != zipStream) {
                zipStream.flush();
                zipStream.close();
            }
            if (null != zipSource) zipSource.close();
        } catch (IOException exception) {
            exception.printStackTrace();
        }
    }
    
    // 如果压缩包创建成功，提供给用户下载
    if (zipFile.exists()) {
        downImg(response, zipFileName, strZipPath);
        // 下载完成后删除临时压缩包
        zipFile.delete();
    }
}

// 下载生成的压缩包
public void downImg(HttpServletResponse response, String filename, String path) {
    if (filename != null) {
        FileInputStream is = null;
        BufferedInputStream bs = null;
        OutputStream os = null;
        try {
            File file = new File(path);
            if (file.exists()) {
                // 设置响应头
                response.setHeader("Content-Type", "application/octet-stream");
                // 解决中文文件名乱码
                response.setHeader("Content-Disposition", "attachment;filename=" + 
                    new String(filename.getBytes("gb2312"), "ISO8859-1"));
                
                is = new FileInputStream(file);
                bs = new BufferedInputStream(is);
                os = response.getOutputStream();
                
                byte[] buffer = new byte[1024];
                int len = 0;
                while ((len = bs.read(buffer)) != -1) {
                    os.write(buffer, 0, len);
                }
            }
        } catch (IOException ex) {
            ex.printStackTrace();
        } finally {
            // 关闭流
            try {
                if (is != null) is.close();
                if (bs != null) bs.close();
                if (os != null) {
                    os.flush();
                    os.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
```

此示例演示了如何将多个文件打包成ZIP压缩包并提供下载。实现的关键点包括：使用`ZipOutputStream`创建压缩包，为每个文件创建`ZipEntry`条目，使用缓冲区提高读写效率，以及在下载完成后删除临时压缩包文件。 

多文件打包下载功能需要注意内存管理，特别是在处理大文件时。示例中使用了缓冲区来减少内存占用，同时及时关闭流防止资源泄漏。对于更大的文件，可以考虑将临时压缩包存储在磁盘上而非内存中，以避免内存溢出问题。

### 中文文件名乱码处理 ###

中文文件名在文件下载过程中经常出现乱码问题，这是因为不同浏览器和对HTTP头部的编码支持不一致。解决中文文件名乱码问题是实现健壮文件下载功能的关键一环。 

传统解决方案是使用URL编码对文件名进行编码：

```java
// 解决中文文件名乱码问题
response.setHeader("Content-Disposition", "attachment;filename=" + 
        URLEncoder.encode(fileName, StandardCharsets.UTF_8));
```

这种方法对大多数现代浏览器有效，但并非所有浏览器都完全遵循标准。更全面的解决方案是同时处理`ISO-8859-1`编码和`UTF-8`编码：

```java
String encodedFileName = new String(filename.getBytes("gb2312"), "ISO8859-1");
response.setHeader("Content-Disposition", "attachment;filename=" + encodedFileName);
```

还有一种更现代化的方法是使用`RFC 5987`标准定义的编码方式，该标准规定了HTTP头部中`非ASCII`字符的编码方式：

```java
String fileName = "中文文件.txt";
String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8)
        .replaceAll("\+", "%20");
String contentDisposition = "attachment; filename=""
        + encodedFileName + ""; filename*=UTF-8''" + encodedFileName;

response.setHeader("Content-Disposition", contentDisposition);
```

这种方法提供了更好的浏览器兼容性，特别是对于较新的浏览器。在实际应用中，可以根据目标用户使用的浏览器类型选择最合适的编码方式。对于需要广泛兼容性的应用，可以检测User-Agent头来判断浏览器类型，并应用不同的编码策略。

### 动态文件名和路径处理 ###

在实际业务场景中，文件下载功能通常需要动态确定文件名和路径。Spring Boot提供了灵活的路径变量和请求参数机制，支持动态文件下载需求。 

使用路径变量实现动态文件下载：

```java
@GetMapping("/download/{fileId}")
public ResponseEntity<Resource> downloadFile(@PathVariable String fileId) {
    // 根据fileId从数据库查询文件信息
    FileInfo fileInfo = fileService.getFileInfo(fileId);
    if (fileInfo == null) {
        throw new FileNotFoundException("文件不存在");
    }
    
    File file = new File(fileInfo.getFilePath());
    Resource resource = new FileSystemResource(file);
    
    return ResponseEntity.ok()
            .header(HttpHeaders.CONTENT_DISPOSITION, 
                    "attachment; filename="" + fileInfo.getOriginalFileName() + """)
            .body(resource);
}
```

使用请求参数实现动态文件下载：

```java
@GetMapping("/download")
public ResponseEntity<Resource> downloadFile(@RequestParam String fileType,
                                             @RequestParam String date) {
    // 根据参数动态构建文件路径
    String filePath = "/data/files/" + fileType + "/" + date + ".csv";
    File file = new File(filePath);
    
    if (!file.exists()) {
        throw new FileNotFoundException("指定文件不存在");
    }
    
    Resource resource = new FileSystemResource(file);
    String fileName = fileType + "_" + date + ".csv";
    
    return ResponseEntity.ok()
            .header(HttpHeaders.CONTENT_DISPOSITION, 
                    "attachment; filename="" + fileName + """)
            .body(resource);
}
```

动态文件名和路径处理使得文件下载功能更加灵活，可以适应各种业务场景需求。在实际应用中，通常需要结合数据库查询或配置服务来动态确定文件位置和名称。

## 四、 异常处理与安全性 ##

### 异常处理机制 ###

健壮的文件下载功能必须具备完善的异常处理机制。在文件下载过程中可能出现的异常情况包括文件不存在、文件读取错误、权限不足等。Spring Boot提供了多种异常处理机制，可以优雅地处理这些错误情况。 

使用`@ExceptionHandler`处理下载异常：

```java
@RestControllerAdvice
public class FileDownloadExceptionHandler {
    
    @ExceptionHandler(FileNotFoundException.class)
    public ResponseEntity<String> handleFileNotFound(FileNotFoundException ex) {
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ex.getMessage());
    }
    
    @ExceptionHandler(IOException.class)
    public ResponseEntity<String> handleIOException(IOException ex) {
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body("文件读取错误: " + ex.getMessage());
    }
    
    @ExceptionHandler(SecurityException.class)
    public ResponseEntity<String> handleSecurityException(SecurityException ex) {
        return ResponseEntity.status(HttpStatus.FORBIDDEN)
                .body("权限不足: " + ex.getMessage());
    }
}
```

在下载方法中主动检查文件状态：

```java
@GetMapping("/download/{fileName}")
public ResponseEntity<Resource> downloadFile(@PathVariable String fileName) {
    Path filePath = Paths.get(FILE_DIRECTORY).resolve(fileName).normalize();
    Resource resource = new FileUrlResource(filePath.toUri());
    
    // 检查文件是否存在
    if (!resource.exists()) {
        throw new FileNotFoundException("文件未找到: " + fileName);
    }
    
    // 检查文件是否可读
    if (!resource.isReadable()) {
        throw new FileReadException("文件不可读: " + fileName);
    }
    
    // 设置响应头
    HttpHeaders headers = new HttpHeaders();
    headers.add(HttpHeaders.CONTENT_DISPOSITION, 
            "attachment; filename="" + resource.getFilename() + """);
    
    return ResponseEntity.ok()
            .headers(headers)
            .body(resource);
}
```

通过合理的异常处理，可以提高文件下载功能的用户体验和系统稳定性。良好的异常处理应包含适当的错误信息提示和正确的HTTP状态码返回，帮助客户端准确识别和处理错误情况。

### 安全性考虑 ###

文件下载功能如果实现不当，可能成为系统安全漏洞的源头。常见的安全风险包括路径遍历攻击、未授权访问和敏感信息泄露。确保文件下载功能的安全性至关重要。

防止路径遍历攻击： 路径遍历攻击是指攻击者通过构造特殊文件名（如"`../../../etc/passwd`"）访问系统敏感文件。

防止这种攻击的关键是对文件路径进行规范化和验证：

```java
@GetMapping("/download/{fileName:.+}")
public ResponseEntity<Resource> downloadFile(@PathVariable String fileName) {
    // 验证文件名是否合法
    if (!isValidFileName(fileName)) {
        return ResponseEntity.status(HttpStatus.FORBIDDEN).body(null);
    }
    
    Path filePath = Paths.get(FILE_DIRECTORY).resolve(fileName).normalize();
    
    // 验证最终路径是否在允许的目录内
    if (!filePath.startsWith(FILE_DIRECTORY)) {
        return ResponseEntity.status(HttpStatus.FORBIDDEN).body(null);
    }
    
    Resource resource = new FileSystemResource(filePath.toFile());
    
    if (!resource.exists()) {
        return ResponseEntity.notFound().build();
    }
    
    // 其余下载逻辑...
}

private boolean isValidFileName(String fileName) {
    // 文件名不能包含路径遍历序列或特殊字符
    return !fileName.contains("..") && !fileName.contains("/") && 
           !fileName.contains("\") && fileName.matches("[a-zA-Z0-9._-]+");
}
```

限制可下载文件类型： 根据业务需求限制可下载的文件类型，防止敏感文件被非法下载：

```java
// 允许下载的文件类型白名单
private static final Set<String> ALLOWED_FILE_TYPES = Set.of(
    "pdf", "doc", "docx", "xls", "xlsx", "jpg", "png", "txt"
);

@GetMapping("/download/{fileName:.+}")
public ResponseEntity<Resource> downloadFile(@PathVariable String fileName) {
    // 获取文件扩展名并验证
    String fileExtension = getFileExtension(fileName);
    if (!ALLOWED_FILE_TYPES.contains(fileExtension.toLowerCase())) {
        return ResponseEntity.status(HttpStatus.FORBIDDEN).body(null);
    }
    
    // 其余下载逻辑...
}

private String getFileExtension(String fileName) {
    int lastDotIndex = fileName.lastIndexOf('.');
    return (lastDotIndex == -1) ? "" : fileName.substring(lastDotIndex + 1);
}
```

添加身份验证和授权检查： 对于包含敏感信息的文件下载，必须添加身份验证和授权检查：

```java
@GetMapping("/download/{fileId}")
public ResponseEntity<Resource> downloadFile(@PathVariable String fileId,
                                             @AuthenticationPrincipal User user) {
    // 检查用户是否有权限下载该文件
    if (!fileService.hasDownloadPermission(user.getId(), fileId)) {
        return ResponseEntity.status(HttpStatus.FORBIDDEN).body(null);
    }
    
    FileInfo fileInfo = fileService.getFileInfo(fileId);
    // 其余下载逻辑...
}
```

通过实施这些安全措施，可以显著降低文件下载功能的安全风险，保护系统和数据的安全性。

## 五、 总结 ##

Spring Boot为文件下载功能提供了多种灵活的实现方式，开发者可以根据具体需求选择最合适的方案。

本文详细介绍了基于ResponseEntity、HttpServletResponse和Resource等核心实现方法，以及多文件打包下载、中文文件名处理等高级功能。同时，还探讨了异常处理和安全性考虑等关键话题。 

在选择实现方案时，应考虑以下因素：对于简单的文件下载场景，使用ResponseEntity的方式代码简洁且功能完备；对于需要精细控制HTTP响应的复杂场景，HttpServletResponse方式提供更多灵活性；而对于多文件打包下载等特殊需求，则需要结合ZipOutputStream等Java API实现定制解决方案。 

无论选择哪种实现方式，都需要注意安全性问题，特别是防止路径遍历攻击和未授权访问。同时，合理的异常处理和性能优化也是实现健壮文件下载功能的关键因素。通过遵循本文介绍的最佳实践，开发者可以构建出安全、高效且易于维护的文件下载功能。 

随着Spring Boot框架的持续演进，文件下载的实现方式也可能会有新的改进和优化。
