Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dynamically injecting instances via CDI

In Java EE platform with the CDI there is ability to inject instances of POJO classes. In very simple way we need use @Inject annotation to inject a default instance of some Interface. We also may use qualifiers to inject concrete class to our field. But these solutions are rather static.

I need some more dynamical model of injecting stuff.

Let me introduce my problem: Let's say that we have interface Animal and three classes that implement that interface: Ant, Dog, Elephant. I would like to dynamically inject instance of one of these three classes, and it depends on some variable like string (animal name). In Java SE I'd do it like below:

Map<String, Animal> animalMap = new HashMap<>();
animalMap.put("ant", new Ant());
animalMap.put("dog", new Dog());
animalMap.put("elephant", new Elephant());
...
String animalName = ...;
Animal animal = animalMap.get(animalMap);
animal.doSomething();

So I need something like:

class AnimalManager {
   @Inject // ?
   private Animal animal; // ?

   public void run(String animalName) {
      // based on animalName get new instance of animal and run doSomething()
      ...
      animal.doSomething(); // if animalName is "ant" call the doSomething on Ant class
   }
}

In all classes that implement Animal interface I need use variables with @EJB annotations.

What the best and most proper way to do it in Java EE?

EDIT:
OK based on response from Svetlin Zarev and hwellmann (thanks!) I have created this:

On the beginning we will create Animal model:

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.TYPE})
public @interface AnimalModel {

   Type value();

   enum Type { ANT, DOG, ELEPHANT }

}

Let's create the Animal:

public interface Animal {
   public void eat(Object food);
}

Next, concrete classes:

@AnimalModel(AnimalModel.Type.ANT)
public class Ant implements Animal {

   @Override   
   public void eat(Object food) {
      ...
   }

}

@AnimalModel(AnimalModel.Type.DOG)
public class Dog implements Animal {

   @Override   
   public void eat(Object food) {
      ...
   }

}

@AnimalModel(AnimalModel.Type.ELEPHANT)
public class Elephant implements Animal {

   @Override   
   public void eat(Object food) {
      ...
   }

}

Next, AnimalLiteral:

public class AnimalLiteral extends AnnotationLiteral<AnimalModel> implements AnimalModel {

   private static final long serialVersionUID = 1L;
   private Type type;
   public AnimalLiteral(Type type) {
      this.type = type;
   }

   public Type value() {
      return type;
   }
}

The main component is the Animal factory:

@Dependent
public class AnimalFactory {

   @Inject
   @Any
   private Instance<Animal> animals;

   private static Map<String, AnimalModel.Type> animalMap;

   public AnimalFactory() {
       animalMap = new HashMap<>();
       animalMap.put("ant", AnimalModel.Type.ANT);
       animalMap.put("dog", AnimalModel.Type.DOG);
       animalMap.put("elephant", AnimalModel.Type.ELEPHANT);
   }

   public Animal getAnimal(String animalName) {
      AnimalModel.Type type = animalMap.get(animalName);
      AnimalLiteral literal = new AnimalLiteral(type);
      Instance<Animal> animalInstance = animals.select(literal);
      return animalInstance.get();
   }
}

And client:

public class Client {

   @Inject
   private AnimalFactory animalFactory;

   public void run(String animalName) {
      Animal animal = animalFactory.getAnimal(animalName);
      animal.eat("some food...");
   }

}

I don't know that putting the map animalMap in that place is right...?

like image 220
Roland Avatar asked Nov 07 '15 12:11

Roland


2 Answers

Using Instance<T> in combination with qualifiers is the standard way in CDI to perform dynamic injection.

You need a qualifier with a binding argument, e.g. @Species("ant") to distinguish your implementation classes.

public class AnimalSelector {

    @Inject
    @Any 
    private Instance<Animal> animals;

    public Animal selectAnimalBySpecies(String speciesName) {
        SpeciesLiteral qualifier = new SpeciesLiteral(speciesName);
        return animals.select(qualifier).get();
    }
}

public class SpeciesLiteral extends AnnotationLiteral<Species> implements Species {

    private String name;

    public SpeciesLiteral(String name) {
        this.name = name;
    }

    @Override
    public String value() {
        return name;
    }
}

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

    String value() default "";
}
like image 190
Harald Wellmann Avatar answered Oct 13 '22 00:10

Harald Wellmann


I don't think it's possible to achieve such dynamic injection with CDI, simply because the dependencies are injected when the container instantiates the managed bean. In other words, when you call run() on your AnimalManager, the Animal dependency, would have already been injected.

What you can do is to inject AnimalFactory and on each call just do lets say AnimalFactory.createAnimal(animal); or use something like your map approach.

like image 36
Svetlin Zarev Avatar answered Oct 12 '22 23:10

Svetlin Zarev