Quarkus 2.10.2中RequestScoped下Reactive Rest Client提前关闭问题咨询
问题根源
Quarkus 2.10.x版本对请求作用域(@RequestScoped)Bean的生命周期触发逻辑做了关键调整:
- 在2.9.2及更早版本,请求作用域Bean的
@PreDestroy会等待整个响应式流(Uni/Multi)完全执行完毕后才触发,确保异步操作完成后再销毁Bean资源。 - 升级到2.10.2后,Quarkus会在HTTP响应已发送至客户端时就判定请求处理完成,随即触发
@PreDestroy销毁@RequestScopedBean。
你的场景中,getPAS2CLabOverview返回的是异步Uni对象,HTTP响应发送时,后台的Rest Client请求可能还在执行。此时@PreDestroy被调用关闭了pas2clabService,后续异步流尝试调用该服务时就会触发“客户端已关闭”的异常。
至于@Singleton/@ApplicationScoped能规避问题,是因为这两个作用域的Bean生命周期与请求无关,不会在单个请求结束时触发@PreDestroy,Rest Client能存活到异步流完成。
无法继续使用@RequestScoped的原因
当前Quarkus 2.10.x的请求作用域生命周期模型和响应式异步流的生命周期不兼容:请求作用域Bean的销毁时机绑定到HTTP响应发送节点,而非异步任务的完成节点,无法满足“等待异步操作结束后再关闭资源”的需求。
解决方案
1. 将Rest Client的关闭逻辑绑定到响应式流
移除@PreDestroy方法,在Uni流的终止阶段(无论成功或失败)手动关闭Rest Client:
public Uni<Optional<OverviewDocument>> getPAS2CLabOverview(String labCode) { return pas2clabService.getWorklist() .onItem().transform(response -> translatePAS2CLabWorklistResponse(response, labCode)) .onFailure().recoverWithItem(this::translatePAS2CLabWorklistResponse) .onTermination(() -> { try { pas2clabService.close(); } catch (Exception e) { // 根据业务需求处理关闭异常,比如日志记录 } }); }
这种方式确保Rest Client仅在异步流完全结束后才被关闭,完全脱离Bean生命周期的限制。
2. 手动延长请求上下文生命周期
通过注入RequestContext手动控制请求上下文的终止时机,让其等待异步流完成:
@Inject RequestContext requestContext; public Uni<Optional<OverviewDocument>> getPAS2CLabOverview(String labCode) { return pas2clabService.getWorklist() .onItem().transform(response -> translatePAS2CLabWorklistResponse(response, labCode)) .onFailure().recoverWithItem(this::translatePAS2CLabWorklistResponse) .onTermination(() -> requestContext.terminate()); }
注意:使用该方案需避免请求上下文泄漏,确保每个请求的上下文都能被正确终止。
3. 调整Rest Client的生命周期管理
如果每个请求的baseUri固定,可直接通过Quarkus的依赖注入注入PAS2CLabService(而非手动用RestClientBuilder创建),让Quarkus自动管理其生命周期;若baseUri需动态设置,可考虑将Rest Client设为@Dependent作用域,配合请求作用域Bean使用。
内容的提问来源于stack exchange,提问作者Marc Woerner

