Retour sur DevoxxFR 2015 CDI

Cette année encore, de nombreux Infoteliens ont participé au Devoxx France qui s’est tenu pendant 3 jours du 8 au 10 juin au Palais des Congrès de Paris.

François NASSOY nous fait un retour sur CDI

Introduction

Contexts and Dependancy Injection (CDI) est une spécification (JSR 299) de JAVA EE qui défini une interface de programmation pour la gestion des injections des dépendances.
Le mot d’ordre de CDI pourrait être : Avec CDI, on injecte tout dans tout!
Les principaux services offerts par CDI sont :

  • Contexte : la capacité à lier le cycle de vie et les intérractions des composants stateful ou l’ensemble des contextes est extensible.
  • L’injection de dépendance: La capacité d’injecter des componants de manière typée et de choisir le spectre d’utilisation (developpement / deploiement)

CDI offre aussi, un certain nombre de services complémentaires :

  • L’intégration de l’Expression Language (EL) permettant à tous les objets contextuels d’être utilisés directement dans les pages JSF et/ou JSP.
  • La capacité de décorer les objects injectés
  • La capacité d’associer des interceptors avec des components en utilisant typesafe interceptor bindings
  • Un modèle de notification d’evénement
  • L’ajout d’un nouveau Scope au trois classiques (request, session and application) : scope conversation
  • Une SPI permettant une intégration propre de CDI aux conteneurs tiers

Premier pas avec CDI

Injection de dépendance

L’injection avec CDI est tres simple. En effet, pour injecter un bean, il suffit d’annoter une classe, un attribut de classe, ou une méthode (setter, constructeur) avec l’annotation @Inject.

Injection d’un DAO :

  • Sur l’attribut

    @inject
    private MyClassDao myClassDao
  • Sur le setter

    private MyClassDao myClassyDao
    @inject
    public void setMyClassDao(MyClassDao myClassDao){
    return myClassDao;
    }
  • Sur le constructeur

    private MyClassDao myClassyDao;
    @inject
    public MyClass(MyClassDao myClassDao){
    this.myClassDao = myClassDao;
    }

Qualifier

Il est tres frequent qu’un bean posséde plusieurs implémentations. L’injection d’un tel bean via @inject provoque une erreur d’ambiguité. En effet, qu’elle implémentation utiliser ?
Pour pallier se problème, CDI propose les Qualifier. L’idée est que chaque implémentation soit qualifié, ie. nommé, et qu’à l’injection du bean soit spécifié quelle implémentation doit être utilisée.
Pour mettre en place cela, il faut :

  • Créer autant d’annotation que de Qualifieur annonter @Qualifier
  • Annonter les implémentations avec le Qualifier @NomQualifier
  • Ajouter lors de l’injection le Qualifier : @inject @NomQualifier
  • Création des annotations
    @Qualifier
    @Retention(RUNTIME)
    @Target({FIELD, TYPE, METHOD, PARAMETER})
    public @interface MyFirstImplDao {
    }
     
    @Qualifier
    @Retention(RUNTIME)
    @Target({FIELD, TYPE, METHOD, PARAMETER})
    public @interface MySecondImplDao {
    }
  • Annotation des implémentations

    @myFirstImplDao
    public class MyFirstImplDaoImpl implements MyClassDao {...}
     
    @mySecondImplDao
    public class MySecondImplDaoImpl implements MyClassDao {...}
  • Injection

    @inject @mySecondImplDao
    private myClassDao;

CDI defini un Qualifier par defaut via l’annotation @Default, permettant ainsi de ne pas devoir préciser le qualifier lors de l’injection.

  • L’injection
    @inject
    private myClassDao;
  • Equivaut à l’injection

    @inject @Default
    private myClassDao;

Produces

Toutes applications JAVA, ou presque, utilisent des librairies externes (log4j, Hibernate, etc.). CDI permet nativement d’inject des beans de librairie externe du moment qu’elles soient elles même des librairies CDI.

La différence entre une librairie CDI et une librairie non CDI est la présence ou non d’un fichier, beans.xml, dans l’archive. La librairie CDI devant posséder ce fichier.

CDI propose un mecanisme pour résoudre le problème d’injection de bean d’une librairie non CDI. Ce mécanisme consiste à rendre injectable un bean d’une librairie tiers en utilisant les Producer CDI, annotation @Produces. Ces produers ont pour but de de créer des objects injectables qui peuvent être de types primitifs, des beans ou encore des ressources telles qu’un entityManager, une connection JMS, etc.

Pour rendre un élement (méthode, attribut, beans, ..) injectable, il suffit de le nommer via un Qualifier et de l’annonter via @Produces.

  • Injection d’un type primitif qui ne varie pas au Runtime
    public ClassA {
    private int myNumber = 10;
     
    @Produces @MyNumber
    int getMyNumber() {
    Return myNumber;
    }
    }
    Public ClassB {
    @Inject @MyNumber
    Int myNumber;
    }

    le ClassB.myNumber est directement initialisée via ClassA.getMyNumber() à 10

  • Injection d’un type primitif qui varie au Runtime
    public ClassA {
    private java.util.Random random = new java.util.Random( System.currentTimeMillis() );
     
    java.util.Random getRandom() {
            return random;
    }
     
    @Produces @Random
    int next() {
        return getRandom().nextInt(maxNumber);
    }
    }
    public ClassB {
    @Inject @Random
    Instance<Integer> randomInt;
     
    Int myRandom;
     
    void ClassB() {
    myRandom = randomInt.get()
    }
    }

l’injection se fait sur une déclaration d’une instance contextuelle de l’objet, utilisable ensuite via la méthode get().

  • Sans CDI
    public class MyDAO {
    @PersistanceContext(unitName="cdiEM")
    private EntityManager em;
    }
  • Avec CDI
    public class DatabaseProducer() {
    @Produces
    @PersistanceContext(unitName="cdiEM")
    @MyDatabase
    private EntityManager em;
    }
    public class MyDAO {
    @inject
    @MyDatabase
    private EntityManager em;
    }

Injection d’élement d’une librairie externe comme par exemple un Logger :

  • Sans CDI
    public class MyClass {
    private static final Logger LOG = Logger.getLogger(MyClass.class);
    }
  • Avec CDI
    public class LoggerProducer() {
    @Produces
    public Logger produceLogger(InjectionPoint injectionPoint) {
    return Logger.getLogger(injectionPoint.getMember().getDeclaringClass().getName());
    }
    public class MyClass {
    @Inject
    private static final Logger LOG;
    }

    La méthode du producer à comme paramétre un injectionPoint, permettant entre autre, de récupérer les informatiosn sur le nom de la classe où l’élement est injecté.

Nommage EL

CDI permet de donner un nom EL (Expression Language) aux beans, via l’annotation @Named, afin qu’ils soient accessibles dans les pages JSF.

  • @Named
    public class MyBean {...}
  • Équivaut �
    @Named(myBean)
    public class MyBean {...}
  • Et s’utilise ainsi dans les pages JSF
    <h:outputLabel value="#{myBean.oneMethod}" />

Alternative

CDI propose une autre fonctionnalité tres interressante, la possibilité de définir une implémentation alternative à un bean.

Un bean peut avoir plusieurs implémentations utilisées à des fins différentes et positionnées lors des developpements par injection via leurs qualifiers. CDI propose de définir des implémentations qui pourront être choisies directement au moment du deploiement permettant ainsi de changer une implémentation sans modification de code.

  • Gérer une logique métier spécifique au client determinée au runtime
  • Spécifier des beans qui sont valides suivant des scénarios de deploiement spécifiques
  • Créer des versions MOCK des beans

Pour créer une alternative, il faut :

  • créer une implémentation
  • annoter la classe avec @alternative
  • definir les qualifiers impactés par l’alternative
  • déclarer l’alternative dans le beans.xml
  • Dao bean avec trois implémentations
    @myFirstImplDao
    public class MyFirstImplDaoImpl implements MyClassDao {...}
    @mySecondImplDao
    public class MySecondImplDaoImpl implements MyClassDao {...}
    @mytThirdImplDao
    public class MythirdImplDaoImpl implements MyClassDao {...}
  • Création de l’alternative
    @alternative
    public mockDAO impléments MyClassDao {}
    Ajout des qualifier devant être remplacer par l’alternative (1 ou plusieurs)
    @alternative
    @myFirstImplDao
    @mySecondImplDao
    public MockMyClassDAO impléments MyClassDao {}
  • L’injection des implémentations ne change pas
     
    @inject @mySecondImplDao
    private myClassDao;

    à ce stade, c’est toujours l’implémentation MySecondImplDao qui sera utilisée

  • Activation de l’alternative dans le beans.xml
    <alternatives>
       <class>fr.exemple.alternative.MockMyClassDAO</class>
    </alternatives>

    à partir de cette instant, c’est l’implémentation alternative, ici le mock, qui sera utilisé en lieu et place des implémentations MyFirstImplDao et MySecondImplDao.
    L’alternative ne portant pas le qualifier de l’implémentation MyThirdImplDao, les injections liés à cette implémentation ne seront pas impactées par l’alternative. Ce sera toujours l’implémentation MyThirImplDao qui sera utilisée.

Event

CDI offre aussi la possibilité aux beans de produire et de consommer des evénements (events). Ce mécansime permet une interaction entre beans via un couplage asynchrone sans utiliser JSM. Il permet aussi de synchroniser et capter les changements d’état de bean stateful.

Un évement est matérialisé soit par un event objet soit par un playload et est qualifié via un ou plusiuers event qualifiers. Ces qualifiers permettent de determiner les events à produire ou à observer pour un bean donné.

    Le principe est simple :

  • mise en place d’un event à observer
    • création d’un qualifier pour l’event
    • création de l’event
    • création de la(des) méthode(s) déclanchant l’event
  • mise en place des observer
    • création de la(des) méthode(s) écoutant l’event
  • Qualification de l’event
    @Qualifier
    @Target({FIELD, PARAMETER})
    @Retention(RUNTIME)
    public @interface Updated {}
  • Création de l’event
    @Inject @Updated
    Event<User> userEvent;
  • Création de la méthode déclanchant l’event
    public void myMethod(Event<User> userEvent) {
       ...
       userEvent.fire(user);}
  • Création d’un observateur quelque soit le qualifier de l’event
    public void onAnyUserEvent(@Observes User user) {}

    méthode déclanchée pour tout event de type userEvent

  • Création d’un observateur pour un events spécifique
    public void afterUserUpdate(@Observes @Updated User user) {...}

    méthode déclanchée pour les event qualifiés @Updated

Conclusion

Bien que cet article ne presente qu’un bref appercu des possibilités de l’API CDI, il transparait assez clairement que CDI apporte un vrai plus à JAVA EE6, de part sa facilité d’utilisation et ses nombreuses fonctionnalités. CDI est une remise en cause de la façon de traiter l’injection de dépendances et les AOP. Il simplifie, il réduit, il se débarrasse de l’héritage, des idées dépassées.

Dans le domain des injections de dépences, des AOP, etc., une API revient reguliérement, Spring. Bien que à mon sens Spring ait encore une longueur d’avance sur CDI, CDI ne démerite pas, loin de là même. Il offre une tres bonne altérnative voir un complément. En effet, des mécanismes permettent d’intégrer les deux API.