CompletionHandler 是 Java NIO.2 中精准触发 I/O 完成后逻辑的关键机制,确保下载写入磁盘完毕即刻校验,避免阻塞与轮询,且需调用 force(true) 刷盘、关闭通道、区分异常重试并协同高级客户端完成落盘后校验。

异步读取的 CompletionHandler 是 Java NIO.2 中处理 I/O 完成后自动触发逻辑的关键机制。在文件下载场景中,它能避免阻塞主线程,同时确保下载完成那一刻立即启动校验(如 MD5、SHA-256 或字段完整性检查),而不是依赖轮询或额外监听。
为什么用 CompletionHandler 而不是普通回调或 Future?
它由底层线程池驱动,在 I/O 操作真正结束(字节写入磁盘完毕)时被调用,时机精准;相比 Future.get() 会阻塞,也比手动注册监听更轻量、无资源泄漏风险。尤其适合大文件分块下载+校验的流水线场景。
核心实现:把校验逻辑封装进 completed() 方法
以使用 AsynchronousFileChannel 写入下载内容为例,关键不是“怎么写”,而是“写完立刻校验”:
- 在
completed()回调中,先调用channel.force(true)确保数据刷盘(防止缓存未落盘导致校验失败) - 关闭通道(
channel.close()),避免句柄泄漏 - 启动校验:可直接计算本地文件哈希,也可读取服务端返回的
X-ChecksumHeader 后比对 - 校验失败时,抛出自定义异常(如
IntegrityCheckException)并记录日志,便于上层重试或告警
注意 handle() 方法里的异常处理陷阱
failed() 会被 I/O 异常(如磁盘满、权限不足)触发,但常见误区是只打印日志就结束。实际应:
- 区分异常类型:
IOException可重试,SecurityException需人工介入 - 若已写入部分数据,根据业务决定是否删除临时文件(避免残留脏数据)
- 将失败原因通过
CompletableFuture或事件总线透传给调度层,触发降级策略(如切 CDN 源、启用备用校验方式)
与 Spring WebClient 或 OkHttp 的协同建议
纯 NIO 场景少,多数项目用高级 HTTP 客户端。此时 CompletionHandler 不直接用于网络请求,而是用于“响应体落地后”的本地校验阶段:
- 用 WebClient 下载到
Path,设置bodyToMono(ByteArrayResource.class)并写入临时文件 - 在
doOnSuccess中启动异步校验任务 —— 这里可复用相同的CompletionHandler模式,用AsynchronousFileChannel.open()打开刚写好的文件,再立即读取头尾/全量做哈希 - 校验结果作为 Mono 流的一部分,和下载响应一起返回,便于统一熔断或指标上报
不复杂但容易忽略:校验必须基于最终落盘的文件,而非内存缓冲或未 flush 的 channel。CompletionHandler 提供了这个“真实完成”的锚点。

















