dependency-injection - 如果存在另一个相同类型的 CDI bean,则否决 CDI bean

标签 dependency-injection cdi

不确定我的想法是否正确,我正在[在 CDI] 中寻找与 Spring 中类似的东西 - @ConditionalOnMissingBean - 允许你告诉 spring - 仅创建如果指定的 bean 丢失。

我尝试过使用扩展,看起来可以点击多个事件,并使用它们来否决 bean。一种方法可能是在此阶段使用 BeanManager,并查找已存在的 bean,如果它包含您要注入(inject)的 bean,则否决此 bean。但是,只有当我们查看了所有 bean 时,这才有效。

AfterBeanDiscovery 看起来很合适,但是,在调用它之前,验证失败,提示存在多个相同类型的 bean。

如果我能在这里得到一些帮助,那就太好了。

最佳答案

您的问题很有趣,可以使用 CDI 扩展来解决(实际上几乎如您所描述的那样),请参阅下面的简单、有效、概念验证的实现。它很天真,因为它不处理例如生产者方法/字段,并且可能会丢失更多。

CDI 扩展确实很棒且功能强大,但可能相当技术性,所以让我们首先讨论其他替代方案。

  1. 特化:也许您的用例明确记录您提供 SomeService 的默认实现就足够了通过,比如说,public class SomeServiceDefaultImpl为了覆盖它,开发人员应该这样做:

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

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

  2. 限定符:如果此服务仅在一个地方/几个地方使用,并且仅在您的代码中使用,您可以限定您的 SomeServiceDefaultImpl使用自定义限定符,例如 @MyDefaultImpl 。然后注入(inject)Instance<SomeService> ,首先查找不合格的实例,如果不满足,则查找合格的实例 - 类似于:

    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();
        }
    }
    
  3. 提供默认实现 @Vetoed从而迫使代码的客户端提供实现。如果客户端想要使用默认值,他们可以简单地使用生产者。


话虽如此,下面的实现是一个概念证明:

  • 要求默认实现上存在以下注释:

    @Target({ TYPE, METHOD, FIELD })
    @Retention(RUNTIME)
    @Documented
    public @interface ConditionalOnMissingBean {
        Class<?> value();
    }
    

    value()是必需的,表示“默认”的 bean 类型。您的实现可以更智能,即从实际的默认实现中检测 bean 类型,但是,嘿,这只是一个概念证明!

  • 公然无视生产者!

  • 经过轻微测试,因此可能存在邪恶的极端情况,所以要小心!

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

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);
    }
}

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

@ApplicationScoped
@ConditionalOnMissingBean(SomeService.class)
public class SomeServiceDefaultImpl implements SomeService {

    @Override
    public String doSomeCalculation() {
        return "from default implementation";
    }
}

关于dependency-injection - 如果存在另一个相同类型的 CDI bean,则否决 CDI bean,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47771999/

相关文章:

cdi - 如何强制使用 CDI 生产者方法?

java - JAX-RS:Stateless、Singleton、RequestScoped 混淆

jsf - 在 JSF 中使用 RequestScoped bean 扩展 PartialViewContext

java - CDI:手动获取用泛型声明的类的 bean 实例

java - cdi - 扩展或带有 beans 的简单 jar

grails - 如何将springSecurityService注入(inject)自定义监听器

java - 如何使用 Guice 注入(inject) Google App Engine 数据存储?

oop - 使用构造函数依赖注入(inject)时提供依赖项的后备/默认值?

c# - 如何在 ASP.NET Core 健康检查中注入(inject)依赖项

java - 在 Guice 中使用最终字段进行字段注入(inject)