Injection de dépendance : Guice et Spring

Présentation de l'injection de dépendance

Principes

Lorsque l'on développe un programme en java, afin d'éviter de tout avoir dans un seul fichier, le développeur écrit plusieurs classes (une classe par fichier). Cela permet non seulement d'assurer une meilleure lisibilité mais aussi une meilleure maintenance.

Seulement lorsque l'on passe en plusieurs classes, on souhaite pouvoir enlever une classe et la remplacer par une autre classe. Cela n'est malheuresement pas possible en java de base, il faut passer par de l'injection de dépendance. Cela permet entre autre "d'échanger" une classe par une autre et d'avoir plusieurs configurations d'application.

Il existe 4 types d'injections de dépendances :
  • Injection par constructeur
  • Injection par interface
  • Injection par mutateur
  • Injection par champs

  • On peut faire de l'injection de dépendance à la main sans utiliser d'outils ou bien utiliser des frameworks (voir plus bas)

    Histoire

    Tout d'abord :
    Lors de la séparation d'un code en plusieurs classes, nous avons une dépendance directe d'une classe a une autre. Nous séparons le code général en une classe A et une classe B. La classe A doit utiliser une méthode de la classe B.



    Cela se fait donc dans le code de A comme suit :
    public class A {
       public static void main(String[] args)
          B b = new B();
          b.someMethod();
       }
    }

    Avantages :
  • Rapide à developper
  • Inconvénients :
  • Statique
  • Disperse les dépendances dans le code

  • Les interfaces :
    Le java fournit un moyen de simplifier la gestion des dépendances : l'Interface. Les interfaces en java permettent de définir des méthodes et leurs paramètres sans en définir le code. Ces interfaces sont ensuites implémentées par les classes dont on dépend.
    Ici, on crée une interface I définissant la méthode someMethod() et la classe A ne dépend donc plus de B directement mais de I qui peut être implémenté par n'importe quelle autre classe.




    Cela se fait donc dans le code de A comme suit :
    public class A {
       public static void main(String[] args)
          I b = new B();
          b.someMethod();
       }
    }
    Comme nous pouvons le constater, nous avons gardé le new ce qui fait qu'il reste une dépendance indirecte dans A.

    Avantages :
  • Toujours rapide à developper
  • Possibilité de changer d'implémentation
  • Inconvénients :
  • Dépendance toujours là
  • Disperse les dépendances dans le code

  • Les Factories :
    Afin d'avoir du code java propre, la comunauté java à instauré des principes de programmations. Ces principes sont appelés des Design Pattern. Parmis ces design pattern on peut trouver le pattern factory. Ce pattern permet d'avoir une classe factory qui va gérer les dépendances. Cette factory possède des méthodes qui vont instancier la dépendance (ici B) et la retourner. Chaque fois qu'une dépendance devra être résolue (besoin d'un objet de type I) , la classe appelante utilisera la factory.




    Cela se fait donc dans le code de factory comme suit :
    public class factory {
       public I getDependency()
          return new B();
       }
    }

    Et dans A :
    public class A {
       public static void main(String[] args) {
          I b = new factory().getDependency();
          b.someMethod();
       }
    }


    Avantages :
  • Possibilité de changer d'implémentation
  • Centralisation des dépendances à un seul endroit
  • Inconvénients :
  • Lourd à utiliser
  • Pas connu par tous les développeurs

  • L'injection par constructeur :
    Les developpement d'applications java ayant besoin de plus en plus de tests, l'injection par factory ne suffit plus. Effectivement, cette dernière ne permet pas d'avoir une configuration de production et une configuration de tests. Une solution pour permettre de choisir le mode de lancement (configuration) est de passer l'implémentation directement dans le constructeur de notre classe (ici A)




    Cela se fait donc dans le code de A comme suit :
    public class A {
       I inst;

       public A(I inst) {
          this.inst=inst;
       }

       public void doWork() {
          inst.someMethod();
       }
    }

    Et dans la classe appelante :
    public static void main(String[] args) {
       A a = new A(factory().getDependency());
       a.doWork();
    }


    Avantages :
  • Possibilité de changer d'implémentation
  • Possibilité de gérer les dépendances au plus haut de l'application
  • Inconvénients :
  • Dispersion du code (classes et sous classes)

  • Cette méthode est l'une des plus "propre" et des plus pratique pour faire de l'injection de dépendance. Cependant, des frameworks existent pour éviter au developpeur de gérer ses injections.

    Les frameworks

    Il existe peu de frameworks pour faire de l'injection de dépendances, je ne présenterai que les 2 plus grands frameworks et les plus récents sur ce site.

    Voici certains frameworks recencés à ce jour :
  • PicoContainer (injection par constructeur)
  • Excalibur (injection par Interface): anciennement connu sous le nom d'Avalon
  • Spring IoC (injection par mutateur)
  • Google Guice (injection par champs et constructeur)

  • Les frameworks PicoContainer et Excalibur (sous son ancien nom Avalon) ont été les premiers à apparaitre comme frameworks d'injection de dépendance. Guice et Spring étants très récents


    Voici un rapide résumé du fonctionnement des frameworks PicoContainer et Excalibur

    PicoContainer :
    PicoContainer a un fonctionnement proche de l'injection par constructeur vue ci dessus. Ce dernier requiert que la classe utilisant la dépendance ai un constructeur permettant de définir l'implémentation. L'application instancie ensuite le picoContainer (par défaut DefaultPicoContainer) et lui faire enregistrer quelle implémentation est associée a quelle interface.
    Pour finir, le framework se charge de construire les élements qui ont besoin d'utiliser des dépendances.

    Excalibur :
    Excalibur fonctionne via de l'injection par interface. Il est donc requis de crée une interface qui va permettre d'injection la dépendance (en plus de l'interface qui définit les méthodes de la dépendance). Cette interface possèdera une méthode permettant de mettre en place l'implémentation de la dépendance.
    Notre classe devra implémenter cette interface et utilisera le code de la méthode pour définir son implémentation de la dépendance. Elle pourra ensuite utiliser cette dernière pour n'importe quelle autre méthode.
    L'application instancie ensuite un Container dans lequel est enre