从Spring控制器下载文件

从Spring控制器下载文件

技术背景

在开发Web应用时,经常会遇到需要让用户从网站下载文件的需求,比如PDF、CSV等格式的文件。这些文件可能是预先存在的,也可能是在代码中动态生成的。Spring框架为处理文件下载提供了多种方式,开发者可以根据具体需求选择合适的实现方法。

实现步骤

1. 使用HttpServletResponse输出流

这是一种较为传统的方式,通过获取HttpServletResponse的输出流,将文件内容写入其中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@RequestMapping(value = "/files/{file_name}", method = RequestMethod.GET)
public void getFile(
@PathVariable("file_name") String fileName,
HttpServletResponse response) {
try {
// get your file as InputStream
InputStream is = ...;
// copy it to response's OutputStream
org.apache.commons.io.IOUtils.copy(is, response.getOutputStream());
response.flushBuffer();
} catch (IOException ex) {
log.info("Error writing file to output stream. Filename was '{}'", fileName, ex);
throw new RuntimeException("IOError writing file to output stream");
}
}

2. 使用ResourceHttpMessageConverter

Spring内置的ResourceHttpMessageConverter可以方便地处理文件下载,会自动设置content-lengthcontent-type

1
2
3
4
5
@RequestMapping(value = "/files/{file_name}", method = RequestMethod.GET)
@ResponseBody
public FileSystemResource getFile(@PathVariable("file_name") String fileName) {
return new FileSystemResource(myService.getFileFor(fileName));
}

3. 使用HttpEntityResponseEntity

这两种方式可以更灵活地设置响应头,适用于不同的文件类型和需求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@RequestMapping(value = "/files/{fileName}", method = RequestMethod.GET)
public HttpEntity<byte[]> createPdf(
@PathVariable("fileName") String fileName) throws IOException {

byte[] documentBody = this.pdfFramework.createPdf(filename);

HttpHeaders header = new HttpHeaders();
header.setContentType(MediaType.APPLICATION_PDF);
header.set(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=" + fileName.replace(" ", "_"));
header.setContentLength(documentBody.length);

return new HttpEntity<byte[]>(documentBody, header);
}

核心代码

以下是几种常见的文件下载实现代码示例:

使用ResponseEntity<Resource>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Controller
public class DownloadController {
@GetMapping("/downloadPdf.pdf")
public ResponseEntity<Resource> downloadPdf() {
FileSystemResource resource = new FileSystemResource("/home/caco3/Downloads/JMC_Tutorial.pdf");
MediaType mediaType = MediaTypeFactory
.getMediaType(resource)
.orElse(MediaType.APPLICATION_OCTET_STREAM);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(mediaType);
ContentDisposition disposition = ContentDisposition
.inline() // or .attachment()
.filename(resource.getFilename())
.build();
headers.setContentDisposition(disposition);
return new ResponseEntity<>(resource, headers, HttpStatus.OK);
}
}

生成并下载文本文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@RequestMapping(value = "/download", method = RequestMethod.GET)
public ResponseEntity<byte[]> getDownloadData() throws Exception {

String regData = "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.";
byte[] output = regData.getBytes();

HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.set("charset", "utf-8");
responseHeaders.setContentType(MediaType.valueOf("text/html"));
responseHeaders.setContentLength(output.length);
responseHeaders.set("Content-disposition", "attachment; filename=filename.txt");

return new ResponseEntity<byte[]>(output, responseHeaders, HttpStatus.OK);
}

最佳实践

  • 设置正确的响应头:根据文件类型设置合适的Content-TypeContent-Disposition,确保浏览器能正确处理文件下载。
  • 避免内存溢出:对于大文件,尽量使用流的方式处理,避免将整个文件加载到内存中。
  • 错误处理:在文件下载过程中,要对可能出现的异常进行捕获和处理,给用户友好的提示。

常见问题

1. 文件在浏览器中显示而不是下载

可以通过设置Content-Dispositionattachment来强制浏览器下载文件。

1
response.setHeader("Content-Disposition", "attachment; filename=somefile.pdf");

2. 文件名包含特殊字符导致问题

在设置文件名时,建议对特殊字符进行处理,如将空格替换为下划线。

1
2
header.set(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=" + fileName.replace(" ", "_"));

3. 下载的文件大小为0

检查文件路径是否正确,文件是否存在,以及文件读取和写入过程中是否出现异常。


从Spring控制器下载文件
https://119291.xyz/posts/2025-04-21.downloading-file-from-spring-controllers/
作者
ww
发布于
2025年4月22日
许可协议