如果存在另一个相同类型的CDI bean,请注意

时间:2017-12-12 11:54:37

标签: dependency-injection cdi

我不确定我是否正确思考这个问题,我在[CDI]中寻找类似于我们在Spring中的内容 - @ConditionalOnMissingBean - 它允许你告诉spring - 只在bean创建时才创建指定缺失。

我尝试过使用扩展程序,看起来可以点击几个事件,然后将它们用于VETO bean。一种方法可能是在此阶段使用BeanManager,并查找已存在的bean,如果它包含您要注入的bean,则VETO这一个。但是,只有当我们看到所有的豆子时,这才有效。

AfterBeanDiscovery看起来合适,但是在调用之前,验证失败,抱怨多个相同类型的bean。

如果我能在这里得到一些帮助,那会很棒。

1 个答案:

答案 0 :(得分:2)

你的问题很有趣,可以使用CDI扩展来解决(实际上几乎就像你描述的那样),请参阅下面的一个天真的,有效的,概念验证实现。这是天真的,因为它没有处理,例如生产者方法/领域,可能会缺少更多。

CDI扩展非常强大,但可能相当技术性,所以我们先讨论其他替代方案。

  1. 专业化:也许您的用例明确记录您提供SomeService的默认实施,例如public class SomeServiceDefaultImpl并按顺序要覆盖它,开发人员应该这样做:

    @Specializes
    public class SomeServiceSpecialImpl extends SomeServiceDefaultImpl {...}
    

    还要考虑替代方案,如John Ament的评论中所述。

  2. 限定符:如果此服务仅在一个地方/几个地方使用且仅在您的代码中使用,则可以使用自定义限定符限定SomeServiceDefaultImpl,例如{ {1}}。然后注入一个@MyDefaultImpl,首先查找一个不合格的实例,如果不满足,请查找符合条件的内容:

    Instance<SomeService>
  3. 提供private SomeService someService; @Inject void setSomeServiceInstance(Instance<SomeService> s) { // not tried, please adapt as needed if( s.isUnsatisfied() ) { someService = s.select(new MyDefaultImplAnnotation()).get(); } else { someService = s.get(); } } 的默认实现,以强制代码的客户端提供实现。如果客户想要使用默认值,他们可以简单地使用生产者。

  4. 如上所述,下面的实现是一个概念证明:

    • 要求以下注释出现在默认实现中:

      @Vetoed

      @Target({ TYPE, METHOD, FIELD }) @Retention(RUNTIME) @Documented public @interface ConditionalOnMissingBean { Class<?> value(); } 是必需的,表示“默认”的bean类型。你的实现可以更聪明,即从实际的默认实现中检测bean类型,但是,嘿,这只是一个概念证明!

    • 公然无视生产者!

    • 经过轻微测试,所以可能存在邪恶的角落情况,所以请小心!

    除了代码之外,您还需要扩展的所有编排(META-INF / services / javax.enterprise.inject.spi.Extension,beans.xml)。

    value()

    服务接口的默认实现示例如下:

    import java.util.HashMap;
    import java.util.Map;
    
    import javax.enterprise.event.Observes;
    import javax.enterprise.inject.spi.AfterBeanDiscovery;
    import javax.enterprise.inject.spi.AnnotatedType;
    import javax.enterprise.inject.spi.Bean;
    import javax.enterprise.inject.spi.BeanAttributes;
    import javax.enterprise.inject.spi.BeanManager;
    import javax.enterprise.inject.spi.Extension;
    import javax.enterprise.inject.spi.InjectionTargetFactory;
    import javax.enterprise.inject.spi.ProcessAnnotatedType;
    
    public class ConditionalOnMissingBeanExtension implements Extension {
    
        private Map<Class<?>, AnnotatedType<?>> map = new HashMap<>();
    
        <T> void processAnnotatedType(@Observes ProcessAnnotatedType<T> pat) {
            AnnotatedType<?> annotatedType = pat.getAnnotatedType();
            ConditionalOnMissingBean annotation = annotatedType.getAnnotation(ConditionalOnMissingBean.class);
            if( annotation != null ) {
                map.put(annotation.value(), annotatedType);
                pat.veto();
            }
        }
    
        void afterBeanDiscovery(@Observes AfterBeanDiscovery abd, BeanManager beanManager) {
            map.entrySet().stream()
                .filter(e -> doesNotHaveBeanOfType(beanManager, e.getKey()))
                .map(e -> defineBean(beanManager, e.getValue()))
                .forEach(abd::addBean);
            map = null;
        }
    
        private boolean doesNotHaveBeanOfType(BeanManager beanManager, Class<?> type) {
            return beanManager.getBeans(type).isEmpty();
        }
    
        private <T> Bean<T> defineBean(BeanManager beanManager, AnnotatedType<T> annotatedType) {
            BeanAttributes<T> beanAttributes = beanManager.createBeanAttributes(annotatedType);
            InjectionTargetFactory<T> injectionTargetFactory = beanManager.getInjectionTargetFactory(annotatedType);
            return beanManager.createBean(beanAttributes, annotatedType.getJavaClass(), injectionTargetFactory);
        }
    }