mercredi 9 avril 2014

@Any ou comment utiliser CDI pour récupérer les implémentations d'une interface

Problème


Itérer sur des implémentation d'interface afin d'appeler une méthode commune a toutes les implémentations car partagé par l'interface. (Implémentations différentes pour chaque classe) Imaginons l'interface AnimalIfc ayant une méthode getType() :
public interface AnimalIfc {
     String getType();
}
Et des implémentations :
 @ChienQualifier
 public class Chien extends AnimalIfc{

  public Chien(){
   super();
  }

  @Qualifier
  @Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
  @Target({ java.lang.annotation.ElementType.TYPE,
   java.lang.annotation.ElementType.METHOD,
   java.lang.annotation.ElementType.PARAMETER,
   java.lang.annotation.ElementType.FIELD })
  public @interface ChienQualifier {

  }

  @Override
  public String getType() {
   return this.getClass().getSimpleName();
  }
 }
 @ChatQualifier
 public class Chat extends AnimalIfc{

  public Chat(){
   super();
  }

  @Qualifier
  @Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
  @Target({ java.lang.annotation.ElementType.TYPE,
   java.lang.annotation.ElementType.METHOD,
   java.lang.annotation.ElementType.PARAMETER,
   java.lang.annotation.ElementType.FIELD })
  public @interface ChatQualifier {

  }

  @Override
  public String getType() {
   return this.getClass().getSimpleName();
  }
 }
 @OiseauQualifier
 public class Oiseau extends AnimalIfc{

  public Oiseau(){
   super();
  }

  @Qualifier
  @Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
  @Target({ java.lang.annotation.ElementType.TYPE,
   java.lang.annotation.ElementType.METHOD,
   java.lang.annotation.ElementType.PARAMETER,
   java.lang.annotation.ElementType.FIELD })
  public @interface OiseauQualifier {

  }

  @Override
  public String getType() {
   return this.getClass().getSimpleName();
  }
 }
Dans une classe on voudrait récupérer le type de tous les animaux simplement sans rien faire si jamais une nouvelle classe implémentant AnimalIfc est crée

Solution


A l'aide de CDI c'est très simple. Il suffit d'utiliser l'annotation @Any qui permet de récupérer les instances implémentant l'interface demandé :
public class AnimauxService {

  @Inject @Any
  private transient Instance<AnimalIfc> animalImplList;

  public AnimauxService(){
   super();
  }

  public List<String> listTypes() {
   final List<String> result = new ArrayList<String>();

   for (AnimalIfc animal : animalImplList) {
    result.add(animal.getType());
   }

   return result;
  }
}


Avec cette méthode si l'on rajoute un type 'Poisson' il suffit de créer la classe correspondant en implémentant l'interface AnimalIfc.
L'intéret est que dans la méthode de liste on n'a rien a changé. Le code reste le même.
N'hésitez pas en cas de question :) !