Quarkus Reactive Websocket Hibernate查询RequestScoped未激活
问题现象
在Quarkus Websocket的@OnMessage处理逻辑中执行响应式Hibernate Panache查询时,响应式调用无报错直接失败,使用.await().indefinitely()阻塞获取结果时捕获到如下异常:
Exception in thread "Thread-752" java.util.concurrent.CompletionException: javax.enterprise.context.ContextNotActiveException: RequestScoped context was not active when trying to obtain a bean instance for a client proxy of PRODUCER_METHOD bean [class=io.quarkus.hibernate.reactive.runtime.ReactiveSessionProducer, id=404040404040] - you can activate the request context for a specific method using the @ActivateRequestContext interceptor binding
按照提示给方法添加@ActivateRequestContext注解、尝试Panache.withTransaction、注入Mutiny.SessionFactory执行查询均无法解决问题,复现代码如下:
@OnMessage @ActivateRequestContext public void onMessage(Session session, String message) { // websocket线程不可被阻塞 new Thread(() -> { System.out.println(MyHibernateEntity.findAll().firstResult().await().indefinitely()); }).start(); }
根本原因
- 手动通过
new Thread()创建的线程完全脱离Quarkus CDI容器管控,@ActivateRequestContext是CDI拦截器注解,仅对CDI容器代理调用的Bean方法生效,手动启动的自定义线程不会自动激活、传播请求上下文,即使把查询逻辑拆分到带注解的单独方法中,只要调用方是手动创建的非受管线程,拦截器就不会生效。 - 响应式Panache的Session绑定在请求上下文上,脱离受管上下文后自然无法获取到Session实例,这也是尝试
Panache.withTransaction、手动注入SessionFactory执行查询依然失败的核心原因——事务、Session都依赖CDI请求上下文。 - 自定义线程中调用
.await().indefinitely()属于错误的响应式用法,会直接阻塞线程,同时打断响应式上下文的传播链路。
修复方案
推荐方案:使用容器受管的执行器+非阻塞链式调用
不要手动创建线程,注入Quarkus自带的支持上下文传播的ManagedExecutor,全程使用响应式链式处理,不要用阻塞式的await()方法:
import io.quarkus.vertx.ManagedExecutor; import jakarta.websocket.OnMessage; import jakarta.websocket.Session; import jakarta.inject.Inject; @Inject ManagedExecutor managedExecutor; @OnMessage public void onMessage(Session session, String message) { MyHibernateEntity.findAll().firstResult() // 把数据库操作切换到受管工作线程执行,避免阻塞Websocket事件循环 .emitOn(managedExecutor) .subscribe().with( result -> { // 处理查询结果 System.out.println(result); // 可在此处通过session对象返回响应消息 }, error -> { // 处理异常 error.printStackTrace(); } ); }
这种方式不需要额外加@ActivateRequestContext,受管执行器会自动传播、激活所需的请求上下文,Panache可以正常获取到Reactive Session实例。
手动控制请求上下文方案(非必要不推荐)
如果确实需要在非受管线程中执行操作,可以注入RequestContextController手动激活/关闭请求上下文:
import jakarta.enterprise.context.control.RequestContextController; import jakarta.inject.Inject; @Inject RequestContextController requestContextController; // 在自定义线程的执行逻辑中调用 public void runQueryInCustomThread() { boolean isActivated = requestContextController.activate(); try { // 在此处执行Panache查询 MyHibernateEntity.findAll().firstResult().await().indefinitely(); } finally { if (isActivated) { requestContextController.deactivate(); } } }
注意:Websocket的
@OnMessage方法默认运行在Vert.x事件循环线程上,绝对禁止在事件循环线程上执行任何阻塞操作(包括.await().indefinitely()、阻塞式IO等),否则会触发Vert.x事件循环阻塞告警,严重影响服务性能。
内容的提问来源于stack exchange,提问作者SIMULATAN
相关产品推荐
相关产品推荐

