Quarkus启动时新线程执行任务触发ContextNotActiveException问题
问题场景
在Quarkus项目中,通过@Observes StartupEvent实现启动时填充缓存,因数据拉取耗时,手动创建新线程执行缓存填充方法,调用注入的SomeRestClient时触发ContextNotActiveException。核心代码如下:
public class CacheClass { private List<SomeObject> cacheData; @Inject SomeRestClient someRestClient; public void init(@Observes StartupEvent e) { // 手动创建线程执行缓存填充 Thread thread = new Thread(() -> fetchDataForCache()); thread.start(); // 其他缓存填充线程 } public void fetchDataForCache() { List<SomeObject> tmpData = someRestClient.fetchSomeData(); // 数据验证与缓存写入逻辑 } // Getter、Setter等其他代码 }
异常栈信息:
Exception in thread "Thread-68" javax.enterprise.context.ContextNotActiveException at io.quarkus.arc.impl.ClientProxies.getDelegate(ClientProxies.java:46) at someRestPackage.SomeRestClient_ClientProxy.arc$delegate(Unknown Source) at someRestPackage.SomeRestClient_ClientProxy.findLatestEntry(Unknown Source) at somePackage.CacheClass(CacheClass.java:15) at somePackage.CacheClass_Subclass.fetchDataForCache$$superforward1(Unknown Source) at somePackage.CacheClass_Subclass$$function$$3.apply(Unknown Source) at io.quarkus.arc.impl.AroundInvokeInvocationContext.proceed(AroundInvokeInvocationContext.java:54) at io.quarkus.arc.runtime.devconsole.InvocationInterceptor.proceed(InvocationInterceptor.java:62) at io.quarkus.arc.runtime.devconsole.InvocationInterceptor.monitor(InvocationInterceptor.java:51) at io.quarkus.arc.runtime.devconsole.InvocationInterceptor_Bean.intercept(Unknown Source) at io.quarkus.arc.impl.InterceptorInvocation.invoke(InterceptorInvocation.java:41) at io.quarkus.arc.impl.AroundInvokeInvocationContext.perform(AroundInvokeInvocationContext.java:41) at io.quarkus.arc.impl.InvocationContexts.performAroundInvoke(InvocationContexts.java:32) at somePackage.CacheClass_Subclass.fetchDataForCache(Unknown Source) at somePackage.CacheClass$1.run(CacheClass.java:11) at java.base/java.lang.Thread.run(Thread.java:829)
异常原因
Quarkus的CDI上下文(如ApplicationScoped、RequestScoped)不会自动传播到手动创建的线程中。注入的SomeRestClient是CDI代理对象,调用其方法时要求当前线程处于活跃的CDI上下文,而手动创建的线程未绑定任何CDI上下文,因此抛出ContextNotActiveException。
修复方案
方案1:使用@Async注解(简单易用)
借助Quarkus的异步API,通过@Async标记缓存填充方法,Quarkus会自动在后台线程执行并处理上下文传播。需确保项目引入quarkus-smallrye-async-api依赖。
修改后代码:
import io.smallrye.common.annotation.Async; public class CacheClass { private List<SomeObject> cacheData; @Inject SomeRestClient someRestClient; public void init(@Observes StartupEvent e) { // 直接调用异步方法,Quarkus自动处理线程与上下文 fetchDataForCache(); // 其他缓存填充异步方法直接调用即可 } @Async public void fetchDataForCache() { List<SomeObject> tmpData = someRestClient.fetchSomeData(); // 数据验证与缓存写入逻辑 } // Getter、Setter等其他代码 }
方案2:使用ManagedExecutor(灵活可控)
注入Quarkus提供的ManagedExecutor,将缓存填充任务提交到托管线程池,它会自动确保CDI上下文传播到任务线程。需引入quarkus-smallrye-context-propagation依赖(若未包含)。
修改后代码:
import jakarta.enterprise.concurrent.ManagedExecutor; public class CacheClass { private List<SomeObject> cacheData; @Inject SomeRestClient someRestClient; @Inject ManagedExecutor managedExecutor; public void init(@Observes StartupEvent e) { // 提交缓存填充任务到托管线程池 managedExecutor.submit(this::fetchDataForCache); // 提交其他缓存填充任务 // managedExecutor.submit(this::fetchAnotherCacheData); } public void fetchDataForCache() { List<SomeObject> tmpData = someRestClient.fetchSomeData(); // 数据验证与缓存写入逻辑 } // Getter、Setter等其他代码 }
方案3:手动传播上下文(不推荐)
若必须手动创建线程,可通过Quarkus Arc API手动捕获并激活上下文,但该方式复杂易出错,仅作备选:
import io.quarkus.arc.Arc; import io.quarkus.arc.InvocationContext; public class CacheClass { private List<SomeObject> cacheData; @Inject SomeRestClient someRestClient; public void init(@Observes StartupEvent e) { // 捕获当前上下文 InvocationContext context = Arc.current().getInvocationContext(); Thread thread = new Thread(() -> { // 在新线程中激活上下文 try (InvocationContext.ContextHandle handle = context.activate()) { fetchDataForCache(); } }); thread.start(); } public void fetchDataForCache() { List<SomeObject> tmpData = someRestClient.fetchSomeData(); // 数据验证与缓存写入逻辑 } // Getter、Setter等其他代码 }
总结
优先使用Quarkus原生的异步机制(@Async或ManagedExecutor)处理启动时的异步缓存填充,避免手动创建线程,这类机制会自动处理CDI上下文传播,彻底规避ContextNotActiveException问题。
内容的提问来源于stack exchange,提问作者Jan5676

