Pass Parameter to Instance of @Inject Bean

Joe Almore picture Joe Almore · Apr 10, 2014 · Viewed 12.7k times · Source

I am using CDI as injection framework, but I have found some limitations in its usage, and this is one of them. I am trying to initialize the creation of a bean instance with runtime values. Example:

@RequestScoped
public class MyNumber {
   int number;

   public MyNumber(int number) {
      this.number = number;
   }

   public String toString() {
      return "Your number is: " + number;
   }
}

public class UseNumber {
   @Inject
   Instance<MyNumber> number;

   public void doStuff() {
      int a = 8;
      MyNumber mN = number.select(a).get(); // ?? - Obviously this does not work.

      System.out.print(mN); // Should print: "Your number is: 8"
   }
}

Please, notice that "a" is a constant in the example, but in practice it is a variable; I clarify this so you don't post an answer with a @Producer to inject the value then in the constructor of MyNumber.

Now, anybody has an idea about how can I do that?

Answer

Antoine Sabot-Durand picture Antoine Sabot-Durand · Apr 11, 2014

I'm not sure what you're trying to do, but from what I understand you want to initialize your bean with data in injection point annotation or at runtime thru programmatic lookup. You can do this by using InjectionPoint meta data in your bean (the only constraint will be to put your bean in dependent scope)

You can do something like this.

First create a qualifier with a non binding value.

@Qualifier
@Target({TYPE, METHOD, PARAMETER, FIELD})
@Retention(RUNTIME)
@Documented
public @interface Initialized {

    @Nonbinding int value() default 0; // int value will be store here 
}

You have to add this qualifier on your bean and analyze InjectionPoint at creation time.

@Initialized
public class MyNumber {
   int number;

   private int extractValue(InjectionPoint ip) {
    for (Annotation annotation : ip.getQualifiers()) {
        if (annotation.annotationType().equals(Initialized.class))
            return ((Initialized) annotation).value();
    }
    throw new IllegalStateException("No @Initialized on InjectionPoint");
  }

   @Inject
   public MyNumber(InjectionPoint ip) {
      this.number = extractValue(ip);
   }

   public String toString() {
      return "Your number is: " + number;
   }
}

You can now inject an initialized number like this:

@Inject
@Initialized(8)
MyNumber number;

If you want to decide the initialization value at runtime, you'll have to use programmatic lookup:

First create the annotation literal for `@Initialized``

public class InitializedLiteral extends AnnotationLiteral<Initialized> implements Initialized {

    private int value;

    public InitializedLiteral(int value) {
        this.value = value;
    }

    @Override
    public int value() {
        return value;
    }
}

Then you can use Instance to create your bean.

public class ConsumingBean {

    @Inject
    @Any
    Instance<MyNumber> myNumberInstance;

    public MyNumber getMyNumberBeanFor(int value) {
     return myNumberInstance.select(new InitializedLiteral(value)).get();
    }
    ...
}

Remember this works only if MyNumber is in dependent scope which makes sense because it's the only way to change initialization value at each injection.