Spring WebFlux用WebClient与AsynchronousFileChannel下载出现空文件问题
问题根因定位
核心原因1:反应式管道从未被触发执行
- 你在
saveFileOnComputer方法中调用DataBufferUtils.write(...)返回的是冷Mono<Void>类型的写入任务流,但你仅链式调用了.then(),既没有调用subscribe()触发执行,也没有调用block()等待任务完成。 - 按照反应式流的规范,没有订阅者的冷Publisher不会执行任何内部逻辑:WebClient不会实际发起HTTP请求拉取图片内容,DataBuffer的写入操作完全不会触发。
- 空文件出现的本质是:代码提前执行了
AsynchronousFileChannel.open(),仅完成了空文件的创建动作,没有任何数据写入流程执行,自然文件大小为0。
核心原因2:DataBuffer释放逻辑错误
- 原代码中
.doOnNext(DataBufferUtils.releaseConsumer())的回调触发时机存在问题:doOnNext会在每个DataBuffer从流中取出、还未执行写入逻辑时就触发内存释放,会直接导致底层内存被回收,就算管道被正常订阅,也可能出现写入内容为空、数据损坏的问题。
次要隐患
- 手动打开的
AsynchronousFileChannel依赖管道执行后的回调关闭,管道未订阅时会造成文件句柄泄漏 - for循环中串行创建下载流的逻辑没有并发控制,后续如果触发大量任务执行,容易一次性打满HTTP连接池与文件句柄资源
保留AsynchronousFileChannel方案的修正实现
public void downloadFilesFromUrl() throws IOException { List<Photo> notDownloadedFiles = // 获取待下载的照片列表 for (Photo photo : notDownloadedFiles) { Path path = Paths.get(pathToFiles + File.separator + photo.getPhotoName()); WebClient client = WebClient.builder().baseUrl(photo.getLoadSource()).build(); Flux<DataBuffer> dataBufferFlux = client .get().accept(MediaType.APPLICATION_OCTET_STREAM) .retrieve() .bodyToFlux(DataBuffer.class); // 显式触发订阅,等待单文件写入流程完整执行 saveFileOnComputer(path, dataBufferFlux).block(); } } private Mono<Void> saveFileOnComputer(Path path, Flux<DataBuffer> dataBufferFlux) throws IOException { // 补充TRUNCATE_EXISTING参数,避免写入时残留旧文件内容 AsynchronousFileChannel asynchronousFileChannel = AsynchronousFileChannel.open( path, StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING ); return DataBufferUtils.write(dataBufferFlux, asynchronousFileChannel) // 仅在DataBuffer被丢弃、未完成写入时触发释放,避免提前回收内存 .doOnDiscard(DataBuffer.class, DataBufferUtils.releaseConsumer()) .doAfterTerminate(() -> { try { asynchronousFileChannel.close(); } catch (IOException ignored) { } }); }
对照验证
你之前替换为DataBufferUtils.write(dataBufferFlux, path, StandardOpenOption.CREATE).block()可以正常运行,本质是两个关键点同时满足:
- 最后显式调用了
.block(),触发了整个反应式管道的订阅执行 - 方法内部自动完成了AsynchronousFileChannel的创建、位置维护、资源关闭全流程,没有提前释放DataBuffer的逻辑错误
内容的提问来源于stack exchange,提问作者izzager
相关产品推荐
相关产品推荐

