Spring Boot MVC应用中用Flux实现PDF下载端点的问题
问题解决:Spring MVC应用中流式返回PDF(避免内存缓冲)
核心结论
在基于Tomcat的Spring Boot MVC应用中,可以实现流式返回PDF(不缓冲整个文件到内存),但不能直接返回Flux<DataBuffer>——因为MVC的消息转换器无法直接处理这种响应式数据流形式,需要改用Spring MVC原生支持的StreamingResponseBody来实现流式输出。
错误原因分析
你遇到的HttpMessageNotWritableException,本质是MVC框架对响应式类型的处理逻辑导致的:MVC默认会将Flux视为普通集合类,尝试把它收集成CollectedValuesList,然后寻找对应的消息转换器,而不存在能处理这种类型+PDF contentType的转换器,因此报错。
解决方案:使用StreamingResponseBody实现流式传输
StreamingResponseBody是Spring MVC专门用于流式响应的接口,它允许直接将数据写入Servlet的OutputStream,完全符合你“避免内存缓冲”的需求。结合WebClient的流式获取能力,可以实现从内部服务器流式读取PDF,再流式输出给客户端的端到端无缓冲传输。
修正后的控制器代码
@GetMapping(path = "/{id}/download-flux", produces = MediaType.APPLICATION_PDF_VALUE) public ResponseEntity<StreamingResponseBody> downloadPdf(@PathVariable final int id) { // 从数据库获取PDF路径 Report report = reportService.getReport(id); // 定义流式响应体 StreamingResponseBody responseBody = outputStream -> { webClient.get() .uri("/" + report.getPath()) .accept(MediaType.APPLICATION_PDF) .retrieve() // 流式获取内部服务器返回的PDF数据 .bodyToFlux(DataBuffer.class) .doOnNext(dataBuffer -> { try { // 将DataBuffer中的数据写入输出流 ByteBuffer byteBuffer = dataBuffer.asByteBuffer(); byte[] bytes = new byte[byteBuffer.remaining()]; byteBuffer.get(bytes); outputStream.write(bytes); // 释放DataBuffer资源(池化对象,必须手动释放避免内存泄漏) DataBufferUtils.release(dataBuffer); } catch (IOException e) { throw new RuntimeException("流式写入PDF失败", e); } }) // 等待所有数据处理完成 .blockLast(); }; return ResponseEntity.ok() .contentType(MediaType.APPLICATION_PDF) .body(responseBody); }
关键注意事项
- DataBuffer资源释放:
DataBuffer是池化对象,使用后必须调用DataBufferUtils.release()释放,否则会导致内存泄漏。 - WebClient的流式能力:
bodyToFlux(DataBuffer.class)确保从内部服务器读取数据时是流式的,不会将整个PDF加载到内存。 - 依赖兼容性:你引入的
spring-boot-starter-webflux依赖可以在MVC应用中正常使用,WebClient本身支持在非WebFlux环境下运行,无需切换到WebFlux服务器。
内容的提问来源于stack exchange,提问作者Datz
相关产品推荐
相关产品推荐

