开发者

Generic CDI producer method not working as expected

I ha开发者_如何学Gove a CDI producer method which - depending on some conditions not relevant to this example - creates objects of different types:

public class TestProducer {

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

but when using this producer, I always get an error in the followin situation:

@Named("test")
public class TestComponent {
   ...
   @Inject public void setA(@TestQualifier String stringValue) {
   ...
   @Inject public void setB(@TestQualifier Integer integerValue) {

It only works when the create method of the producer has the expected type in the method signature:

public class TestProducer {

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

Now the String get's injected correctly, but I have no way to also generate an integer from the producer method. But this is exactly what I want to avoid, since the producer itself should be completely generic.

Am I doing something wrong or is there no way to achieve the behaviour I want?


All CDI documentation makes it clear that CDI does typesafe dependency injection - and it is an exalted property of CDI. IMHO, what you are trying to do is just what CDI tries to avoid. You want the container to cast Object to each type and CDI does not work that way.

The injections points stringValue and integerValue can only receive a bean which has java.lang.String and java.lang.Integer in its list of bean types respectively. java.lang.Object does not satisfy this criterion.

I have two suggestions. First, since you have two or more injection points of different types, create two or more producer methods for that types:

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
    }
  }
// ...

It works if the something condition is just to check the type of the injection point (what I am betting is the case).

However, if the something condition does decide the type using other criteria than the type of the injection point, I'd suggestion to do the "dirty job" yourself: inject the returned value in an Object-typed injection point and does the cast manually:

@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;

The main point is that, unlike some other DI frameworks, CDI does not work against the Java type system - on the contrary, it heavily uses it. Do not try to fight against it but use this aspect of CDI in your favor :)


A producer for Object is strange anyway. I'm not sure if this is forbidden by the spec, or it's a bug, but I think you can make some clever workaround:

public class ValueHolder<T> {
    private T value;

    public T getValue() {
        return value;
    }
}

And then inject a ValueHolder<String> and ValueHolder<Integer>


Its possible create generic objects with CDI produces like that:

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

    }

Works on weld 2.2.0. I think that works on some previous versions as well.


Your initializer methods will look for a managed bean with API types String and Integer, but your producer method bean only has API type (in case of producer method, return type) Object.

You can therefore only use Object in your initializer method injected fields and then discriminate between the types int the body of the receiver, or simply wrap them and the producer method in an actual type that can return Strings or Int (but I'd avoid the generics)

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜