如何基于多限定符注解动态选择CDI Processor Bean?
问题背景
现有多个Processor类,每个负责处理1-2个特定key的CSV行数据。通过CDI注入Instance<Processor>后,原本可以根据每行的key,用processors.select(new ProcessorQualifier(key))匹配对应Processor。但当需要让一个Processor同时处理多个key时,重复添加@ProcAnnotation会触发编译错误,创建可重复注解容器后,又无法通过原有的select方法匹配到对应Bean。
解决方案步骤
1. 修正可重复注解的定义
首先要正确配置可重复注解的结构,原有的@ProcAnnotations配置有误,需要调整:
调整@ProcAnnotation(单个限定符注解)
添加@Repeatable指定容器注解,确保编译器允许重复使用该注解:
@Retention(RUNTIME) @Qualifier @Target({TYPE}) @Repeatable(ProcAnnotations.class) // 指定容器注解 public @interface ProcAnnotation { ProcTypeEnum type(); }
调整@ProcAnnotations(容器注解)
移除多余的@Qualifier、@Stereotype等注解,它仅作为多个@ProcAnnotation的容器:
@Retention(RUNTIME) @Target({TYPE}) public @interface ProcAnnotations { ProcAnnotation[] value(); }
2. 给Processor添加多key支持
现在可以直接给同一个Processor添加多个@ProcAnnotation注解,编译器不会报错:
@ProcAnnotation(type = ProcTypeEnum.key1) @ProcAnnotation(type = ProcTypeEnum.key2) class Key1AndKey2Processor implements Processor { @Override public void processLine(String text) { // 处理key1和key2的逻辑 } }
3. 修改动态选择逻辑
CDI的Instance.select(Qualifier)默认是精确匹配单个限定符,无法匹配带有多个限定符的Bean。因此需要换一种方式:提前构建key到Processor的映射,或遍历实例检查注解。
方式一:初始化时构建映射(推荐)
在Parser初始化阶段,把所有Processor按对应的key分组存入Map,后续直接通过key查询,性能更优:
class Parser { @Inject @Any Instance<Processor> processors; private Map<ProcTypeEnum, List<Processor>> processorMap; @PostConstruct void initProcessorMap() { processorMap = new HashMap<>(); for (Processor processor : processors) { // getAnnotationsByType会自动解开@ProcAnnotations容器,返回所有单个@ProcAnnotation ProcAnnotation[] annotations = processor.getClass().getAnnotationsByType(ProcAnnotation.class); for (ProcAnnotation anno : annotations) { processorMap.computeIfAbsent(anno.type(), k -> new ArrayList<>()).add(processor); } } } public void parseCsv(BufferedReader reader) throws IOException { String text; while (null != (text = reader.readLine())) { String keyStr = getKeyFromLine(text); ProcTypeEnum key = ProcTypeEnum.valueOf(keyStr); List<Processor> matchedProcessors = processorMap.get(key); if (matchedProcessors != null && !matchedProcessors.isEmpty()) { // 此处假设每个key对应一个Processor,可根据需求调整逻辑 matchedProcessors.get(0).processLine(text); } } } private String getKeyFromLine(String text) { // 提取行中key的逻辑 return ""; } }
方式二:遍历实例实时匹配
如果不需要提前初始化,也可以每次处理行时遍历所有Processor实例,检查是否支持当前key:
class Parser { @Inject @Any Instance<Processor> processors; public void parseCsv(BufferedReader reader) throws IOException { String text; while (null != (text = reader.readLine())) { String keyStr = getKeyFromLine(text); ProcTypeEnum key = ProcTypeEnum.valueOf(keyStr); for (Processor processor : processors) { ProcAnnotation[] annotations = processor.getClass().getAnnotationsByType(ProcAnnotation.class); for (ProcAnnotation anno : annotations) { if (anno.type() == key) { processor.processLine(text); break; } } } } } private String getKeyFromLine(String text) { // 提取行中key的逻辑 return ""; } }
关键说明
getAnnotationsByType(ProcAnnotation.class)方法会自动处理可重复注解的容器,直接返回所有添加的@ProcAnnotation实例,无需手动解析@ProcAnnotations容器注解,这是Java反射API对可重复注解的原生支持。
内容的提问来源于stack exchange,提问作者Lbro

