如何在Quarkus中实现可手动切换的自定义CDI作用域?
如何实现可手动切换的自定义CDI作用域(Quarkus环境)
需求说明
需要实现一个类似@RequestScoped的自定义CDI作用域,支持手动启停控制,行为和Quarkus中QuarkusTransaction.begin()/QuarkusTransaction.commit()控制@TransactionScoped一致。
过往尝试及问题
- 早期参考旧方案基于事件观察者实现上下文,但Quarkus要求上下文类必须是Bean才能接收事件;改为
@ApplicationScoped后,每次接收事件时拿到的CustomScopeContextHolder都是全新的空实例,完全无法共享状态。 - 研究Quarkus源码后尝试基于
ManagedContext实现,但它的activate()/deactivate()无参数,无法标识作用域;自己用ThreadLocal<Map<Contextual<?>, ContextualInstance<?>>>存储实例,当前看似可用,但担心Quarkus线程模型下ThreadLocal会有潜在问题(比如Weld用BoundContext替代ThreadLocal规避相关风险)。 - 注意到
CurrentManagedContext是Request/Session上下文的基类,但它属于arc.impl内部包,文档缺失,且存在Quarkus版本变更导致兼容问题的风险。
期望实现效果示例
MyBean.java
@MyScoped public class MyBean { private String value; public String getValue() { return value; } public void setValue(String value) { this.value = value; } }
MyService.java
@ApplicationScoped public class MyService { @Inject MyBean myBean; public void foo() { // myBean不可用,调用会抛出异常 MyScope.init(); // myBean#1 可用 myBean.setValue("foo"); System.out.println(myBean.getValue()); // 输出 "foo" MyScope.deinit(); // myBean不可用,以下代码会抛出异常 // System.out.println(myBean.getValue()); MyScope.init(); // myBean#2 可用 myBean.setValue("bar"); System.out.println(myBean.getValue()); // 输出 "bar" MyScope.deinit(); } }
正确实现步骤
1. 定义自定义作用域注解
创建@MyScoped注解,标记为CDI标准作用域:
import jakarta.enterprise.context.NormalScope; import java.lang.annotation.*; @NormalScope(passivating = true) @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD}) public @interface MyScoped { }
2. 实现ManagedContext子类
基于Quarkus公开的ManagedContext接口实现上下文,用ThreadLocal存储实例,同时标记为@Singleton确保上下文实例唯一:
import io.quarkus.arc.ManagedContext; import jakarta.enterprise.context.spi.Contextual; import jakarta.enterprise.context.spi.CreationalContext; import jakarta.enterprise.inject.spi.BeanManager; import jakarta.inject.Singleton; import java.util.HashMap; import java.util.Map; @Singleton public class MyScopeContext implements ManagedContext { private final ThreadLocal<Map<Contextual<?>, Object>> instanceMap = new ThreadLocal<>(); private final BeanManager beanManager; public MyScopeContext(BeanManager beanManager) { this.beanManager = beanManager; } @Override public Class<? extends jakarta.enterprise.context.spi.Context> getScope() { return MyScoped.class; } @Override public boolean isActive() { return instanceMap.get() != null; } @Override public void activate() { if (isActive()) { throw new IllegalStateException("MyScope 已激活"); } instanceMap.set(new HashMap<>()); } @Override public void deactivate() { if (!isActive()) { throw new IllegalStateException("MyScope 未激活"); } // 销毁作用域内所有Bean,避免内存泄漏 Map<Contextual<?>, Object> instances = instanceMap.get(); instances.forEach((contextual, instance) -> { contextual.destroy(instance, beanManager.createCreationalContext(contextual)); }); instanceMap.remove(); } @Override public <T> T get(Contextual<T> contextual, CreationalContext<T> creationalContext) { if (!isActive()) { throw new IllegalStateException("MyScope 未激活"); } Map<Contextual<?>, Object> instances = instanceMap.get(); @SuppressWarnings("unchecked") T instance = (T) instances.get(contextual); if (instance == null) { instance = contextual.create(creationalContext); instances.put(contextual, instance); } return instance; } @Override public <T> T get(Contextual<T> contextual) { return get(contextual, null); } }
3. 创建手动控制工具类
实现MyScope工具类,通过注入上下文提供静态启停方法:
import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; @ApplicationScoped public class MyScope { private static MyScopeContext context; @Inject public MyScope(MyScopeContext context) { MyScope.context = context; } public static void init() { context.activate(); } public static void deinit() { context.deactivate(); } }
4. 验证与注意事项
- Quarkus会自动扫描
@Singleton标记的上下文类,无需额外配置。 - ThreadLocal在Quarkus Vert.x线程模型下是安全的:事件循环线程单线程执行,不会出现状态污染;工作线程场景下,只要保证上下文启停在同一个线程内即可(符合手动控制的逻辑)。
- 销毁上下文时必须调用Bean的
destroy方法,避免内存泄漏。
内容的提问来源于stack exchange,提问作者Dan
相关产品推荐
相关产品推荐

