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

如何创建可配置CDI Producer?配置依赖注入遇问题求助

动态配置CDI StorageService实现的问题与解决方案

先聊聊你的场景:最开始你用@Storage限定符区分LocalStorage和RemoteStorage两个StorageService实现,注入时通过限定符指定具体实现,运行一切正常。后来想通过外部配置动态切换实现,写了Producer却遇到两个棘手问题:移除@Storage注解后出现依赖歧义,用@Vetoed强制依赖Producer又导致Bean内部的注入值缺失。

下面针对你的两个问题逐一解答:

1. 这是创建可配置动态Producer的正确方式吗?

当然不是,你当前的Producer写法有个核心漏洞:直接用new关键字创建RemoteStorage和LocalStorage实例。这样创建出来的对象完全脱离CDI容器的管理,容器不会处理它们内部的@Inject注解,自然会导致projectId这类依赖注入失败。

正确的思路是让CDI容器负责实例的创建和生命周期管理,你只需要在Producer里根据外部配置选择对应的Bean即可。这里有两种更靠谱的实现方式:

方式一:使用Instance API(简洁易维护)

保留两个实现类上的@Storage限定符,然后在Producer里通过Instance结合自定义的限定符字面量来获取对应Bean实例:

@Singleton
public class StorageServiceProducer {
    @Inject @ConfigurationValue("storage.type")
    private String storageType;

    @Inject
    private Instance<StorageService> storageServiceInstance;

    @Produces
    public StorageService produceStorageService() {
        // 将配置字符串转为枚举类型
        StorageType targetType = StorageType.valueOf(storageType.toUpperCase());
        // 通过自定义限定符字面量筛选对应Bean
        return storageServiceInstance.select(new StorageLiteral(targetType)).get();
    }

    // 自定义限定符字面量类,用于构建@Storage注解实例
    private static class StorageLiteral extends AnnotationLiteral<Storage> implements Storage {
        private final StorageType type;

        public StorageLiteral(StorageType type) {
            this.type = type;
        }

        @Override
        public StorageType value() {
            return type;
        }
    }
}

方式二:使用BeanManager API(底层可控性强)

如果需要更精细的实例创建控制,可以用BeanManager来查找和创建Bean实例:

@Singleton
public class StorageServiceProducer {
    @Inject @ConfigurationValue("storage.type")
    private String storageType;

    @Inject
    private BeanManager beanManager;

    @Produces
    public StorageService produceStorageService() {
        StorageType targetType = StorageType.valueOf(storageType.toUpperCase());
        
        // 动态构建@Storage限定符实例
        Storage storageQualifier = new Storage() {
            @Override
            public Class<? extends Annotation> annotationType() {
                return Storage.class;
            }

            @Override
            public StorageType value() {
                return targetType;
            }
        };

        // 查找符合类型和限定符的Bean集合
        Set<Bean<?>> matchingBeans = beanManager.getBeans(StorageService.class, storageQualifier);
        // 解析出唯一匹配的Bean
        Bean<StorageService> targetBean = (Bean<StorageService>) beanManager.resolve(matchingBeans);
        // 创建上下文并让容器实例化Bean
        CreationalContext<StorageService> ctx = beanManager.createCreationalContext(targetBean);
        
        return targetBean.create(ctx);
    }
}

这两种方式的核心都是让CDI容器主导实例创建,这样Bean内部的依赖注入(比如RemoteStorage里的projectId)就能正常生效,同时也解决了依赖歧义问题——Producer会根据配置明确选择一个具体的Bean实现。

2. 使用@Alternatives需修改beans.xml,如何实现无需改动源码/配置文件切换实现?

其实上面的Producer方案就完全满足你的需求:你只需要修改外部属性文件里的storage.type值(比如改成local或remote),就能动态切换StorageService的实现,不需要修改任何源码或beans.xml。

另外补充一个思路:如果不想保留@Storage限定符,也可以给两个实现类加上不同的@Named注解(比如@Named("localStorage")和@Named("remoteStorage")),然后在Producer里根据配置值,通过Instance.select(NamedLiteral.of(storageType))来获取实例,原理和上面一致。

总结下来,通过Producer结合外部配置的方式,既能实现无源码修改的动态切换,又能保证Bean完全受CDI容器管理,完美解决你遇到的两个核心问题。

内容的提问来源于stack exchange,提问作者Tim

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 07:49:49