如何创建可配置CDI Producer?配置依赖注入遇问题求助
先聊聊你的场景:最开始你用@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

