从Spring Boot Rest服务下载文件
从Spring Boot Rest服务下载文件
我正在尝试从Spring Boot REST服务中下载文件。\n@RequestMapping(path=\"/downloadFile\",method=RequestMethod.GET)\n@Consumes(MediaType.APPLICATION_JSON_VALUE)\npublic ResponseEntity
问题的出现原因:
- 当下载一个非常大的文件时,直接将文件写入响应的输出流(OutputStream),而不阻塞Servlet容器线程,是一个比较好的方法。
解决方法:
- 使用StreamingResponseBody来实现文件下载功能。
- 首先,设置响应的Content-Type和Content-Disposition头信息,以便浏览器正确处理下载文件。
- 然后,返回一个lambda表达式,该表达式将文件的输入流读取到缓冲区,并将缓冲区的内容写入输出流,直到文件被完全写入。这样可以实现文件的逐块下载,避免一次性将整个文件加载到内存中。
代码示例:
("download") public StreamingResponseBody downloadFile(HttpServletResponse response, Long fileId) { FileInfo fileInfo = fileService.findFileInfo(fileId); response.setContentType(fileInfo.getContentType()); response.setHeader( HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=\"" + fileInfo.getFilename() + "\""); return outputStream -> { int bytesRead; byte[] buffer = new byte[BUFFER_SIZE]; InputStream inputStream = fileInfo.getInputStream(); while ((bytesRead = inputStream.read(buffer)) != -1) { outputStream.write(buffer, 0, bytesRead); } }; }
注意事项:
- 使用StreamingResponseBody时,强烈建议配置Spring MVC中用于执行异步请求的TaskExecutor。TaskExecutor是一个抽象执行Runnable的接口。
- 不需要手动关闭输入流(InputStream),因为它是可自动关闭的。只需确保我们返回的是一个lambda表达式,框架会在调用完毕后自动关闭输入流。
更多信息:
- 可以参考以下链接获取更详细的内容:https://medium.com/swlh/streaming-data-with-spring-boot-restful-web-service-87522511c071
关于使用ByteArrayResource和StreamingResponseBody的选择:
- 如果文件不超过10MB并且以字节数组的形式存储在数据库中,那么可以考虑使用ByteArrayResource。
- 如果文件非常大,使用StreamingResponseBody是更好的选择,因为它可以逐块下载文件,避免一次性将整个文件加载到内存中。
问题的出现原因:从Spring Boot REST服务中下载文件时,使用了ByteArrayResource将整个文件加载到内存中,适用于小文件,但对于大文件来说效率较低。
解决方法:对于大文件,可以直接将文件写入OutputStream,避免将整个文件加载到内存中。
以下是一个解决方法的示例代码:
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.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.OutputStream; @RestController @RequestMapping("/app") public class FileDownloadController { private static final String SERVER_LOCATION = "/server/images"; @RequestMapping(path = "/download", method = RequestMethod.GET) public ResponseEntitydownloadFile(@RequestParam("image") String image) throws IOException { File file = new File(SERVER_LOCATION + File.separator + image + ".jpg"); HttpHeaders headers = new HttpHeaders(); headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=img.jpg"); headers.add("Cache-Control", "no-cache, no-store, must-revalidate"); headers.add("Pragma", "no-cache"); headers.add("Expires", "0"); FileSystemResource resource = new FileSystemResource(file); return ResponseEntity.ok() .headers(headers) .contentLength(file.length()) .contentType(MediaType.parseMediaType("application/octet-stream")) .body(resource); } }
以上代码中,使用FileSystemResource将文件直接作为资源返回,避免了将整个文件加载到内存中。这种方法更适用于处理大文件。
在Spring Boot REST服务中从文件下载问题的原因是文件的格式和文件名在下载过程中丢失,并且文件名为“response”。解决方法是添加Content-Disposition HttpHeader,以指定文件名和文件扩展名。另外,还可以使用InputStreamResource或ByteArrayResource来下载文件。使用InputStreamResource时,Spring Boot会自动关闭流;而使用ByteArrayResource时,文件会完全加载到内存中。对于大文件,可以考虑使用StreamingResponseBody来避免内存溢出问题。还可以使用ResourceHttpMessageConverter来确保在使用纯Spring而不是Spring Boot时正常工作,并且可以通过捕获异常来通知客户端发生错误。如果需要下载多个文件,可以在一个请求中下载所有文件。对于文件名中包含空格和括号的情况,可以使用org.apache.commons.io.FilenameUtils.getName(file.getPath())来进行转义。