You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Quarkus中实现可手动切换的自定义CDI作用域?

如何实现可手动切换的自定义CDI作用域(Quarkus环境)

需求说明

需要实现一个类似@RequestScoped的自定义CDI作用域,支持手动启停控制,行为和Quarkus中QuarkusTransaction.begin()/QuarkusTransaction.commit()控制@TransactionScoped一致。

过往尝试及问题

  1. 早期参考旧方案基于事件观察者实现上下文,但Quarkus要求上下文类必须是Bean才能接收事件;改为@ApplicationScoped后,每次接收事件时拿到的CustomScopeContextHolder都是全新的空实例,完全无法共享状态。
  2. 研究Quarkus源码后尝试基于ManagedContext实现,但它的activate()/deactivate()无参数,无法标识作用域;自己用ThreadLocal<Map<Contextual<?>, ContextualInstance<?>>>存储实例,当前看似可用,但担心Quarkus线程模型下ThreadLocal会有潜在问题(比如Weld用BoundContext替代ThreadLocal规避相关风险)。
  3. 注意到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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 10:13:15