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...?
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 "";
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With