通用的CDI生产者方法未按预期工作

4

我有一个CDI生产者方法,根据一些与本示例无关的条件创建不同类型的对象:

public class TestProducer {

  @Produces @TestQualifier
  public Object create(InjectionPoint ip) {
    if(something) {
      return "a String";
    } else {
      return Integer.valueOf(42);
    }
  }

但是,在使用这个生产者时,我总是在以下情况下遇到错误:
@Named("test")
public class TestComponent {
   ...
   @Inject public void setA(@TestQualifier String stringValue) {
   ...
   @Inject public void setB(@TestQualifier Integer integerValue) {

只有当生产者的创建方法在方法签名中具有期望的类型时,它才起作用:

public class TestProducer {

  @Produces @SpringBean
  public String create(InjectionPoint ip) {

现在字符串已经正确注入,但我没有办法从生产者方法中生成一个整数。但这正是我想要避免的,因为生产者本身应该是完全通用的。

我做错了什么还是没有办法实现我想要的行为?


@Bozho:我现在没有代码,但是大概是“无法解析匹配的对象”。当使用调试器时,我还可以验证生产者方法本身根本没有被调用。 - Christian Seifert
4个回答

4
所有的CDI文档都明确指出,CDI进行了类型安全的依赖注入,这是CDI的一个卓越特性。在我看来,你想要做的正是CDI试图避免的。你希望容器将Object转换为每种类型,但CDI并不是这样工作的。
注入点stringValue和integerValue只能接收在其bean types列表中具有java.lang.String和java.lang.Integer的bean。java.lang.Object无法满足此标准。
我的建议有两个。首先,由于您有两个或更多不同类型的注入点,请为该类型创建两个或更多生产者方法:
public class TestProducer {

  @Produces @TestQualifier
  public String createString(InjectionPoint ip) {
    if(something) {
      return "a String";
    } else {
      // Some other value
    }
  }

  @Produces @TestQualifier
  public int createInt(InjectionPoint ip) {
    if(something) {
      return 42;
    } else {
      // Some other value
    }
  }
// ...

如果“something”条件只是用于检查注入点的类型(我打赌这是情况),那么它可以工作。
但是,如果“something”条件使用除注入点类型之外的其他标准来确定类型,则建议您自己完成“肮脏的工作”:将返回值注入到一个对象类型的注入点中,并手动进行转换。
@Named("test")
public class TestComponent {
   ...
   @Inject public void setA(@TestQualifier Object value) {
       String stringValue = (String) value;

   ...
   @Inject public void setB(@TestQualifier Object value) {
       int intValue = (Integer) value;

主要的观点是,与其他一些DI框架不同,CDI不会违背Java类型系统 - 相反,它大量使用它。不要试图对抗它,而是利用CDI的这个方面来获益 :)

1
我不认为它违反了类型系统。CDI生产者应该能够检查注入点,然后检查是否有该类型,如果没有,则抛出异常。 - dexter meyers

4

Object的生产者本来就很奇怪。我不确定这是否被规范禁止,或者这是一个bug,但我认为你可以想出一些巧妙的解决方案:

public class ValueHolder<T> {
    private T value;

    public T getValue() {
        return value;
    }
}

然后注入一个 ValueHolder<String>ValueHolder<Integer>


这实际上是对您在https://dev59.com/l2855IYBdhLWcg3wynmC中提出建议的跟进 - 我想从Spring ApplicationContext注入bean,这需要非常通用的查找。 - Christian Seifert
试试使用ValueHolder - 如果仍然无法工作,则问题不在于对象。让我知道结果。 - Bozho

3

使用CDI Produces可以创建通用对象,示例如下:

  // the wrapper class
    public class Wrapper<T> {
      public final T bean;
      public Wrapper(T bean){
        this.bean = bean;
      }
    }

    // the producer inside some class
    @Produces
    public <T> Wrapper<T> create(InjectionPoint p){
      // with parameter 'p', it is possible retrieve the class type of <T>, at runtime
    }


    // the bean example 1
    public class BeanA {
      public void doFoo(){
        // ...
      }
    }
    // the bean example 2
    public class BeanB {
      public void doBar(){
        // ...
      }
    }


    // the class that uses the produced beans
    public class SomeBean{

//// There on producer method, do you can retrieve the Class object of BeanA and BeanB, from type parameters of Wrapper.

      @Inject
      private Wrapper<BeanA> containerA;
      @Inject
      private Wrapper<BeanB> containerB;

      public void doSomeThing(){
         containerA.doFoo();
         containerB.doBar();
      }

    }

适用于weld 2.2.0。 我认为它也适用于一些早期版本。


1

你的初始化方法将查找具有API类型String和Integer的托管bean,但是你的生产者方法bean只有API类型(在生产者方法的情况下,返回类型)Object。

因此,在你的初始化方法注入字段中,你只能使用Object,然后在接收器的主体中区分类型,或者简单地将它们包装在实际类型中,该类型可以返回字符串或Int(但我会避免使用泛型)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接