Injection de dépendance : Guice et Spring

Google Guice

Google

Google est une société crée par Larry Page et Sergey Brin. Créateurs du moteur de recherche du même nom, la société a vite su évoluer et proposer des services web tels que le mail (Gmail) ou de la visualisation de la planète (Google Earth).

Google propose chaque jour de nouvelles fonctionnalités ou de nouveaux produits et donc un grand besoin d'avoir un code propre et fonctionnel. Pour cela google propose aussi des outils (utilisés en interne chez eux) pour contribuer a ce code propre. Parmis ces outils on peux trouver Google Guice pour l'injection de dépendance.

Google Guice est apparu après la solution de Spring. Spring n'a pas convenu a Google qui cherchait un outil simple et facile à utiliser. Guice à donc été developé en mettant ces 2 aspects en avant. Il ne couvre pas tous les cas que couvre Spring mais offre une facilité et rapidité beaucoup plus évoluée.

Depuis sa version 2.1 (actuellement en version 3.0 depuis mars 2011) Guice respecte la JSR 330 en ce qui concerne les annotations d'injection de dépendance. Il peut donc être remplacé par un autre framework respectant la JSR 330 sans changement de code.

Fonctionnement


Le fonctionnement de Guice repose sur 3 éléments clés :
  • La configuration par modules pour lier les implémentations à leurs interfaces
  • L'annotation Inject pour les objets à injecter
  • L'injection en même est effectuée par un Injector de Spring

  • Contexte de l'application

    Afin de pouvoir comparer correctement les deux solutions, nous reprendrons le même contexte que pour Spring IoC.
    Pour notre exemple nous allons avoir une projet avec une classe principale qui va publier un lien sur un site web. Ce lien pouvant être compliqué nous souhaitons qu'il puisse être simplifié.
    Nous avons donc :

  • Main.java : classe appelée au lancement de l'application, cette dernière va simplement lancer notre classe principale TweetClient.java. Elle utilisera les implémentations BasicShortener et SmsTweeter
  • TweetClient.java : classe principale chargée de publier le lien sur internet (ici nous utiliserons la console). Elle utilisera 2 interfaces : Shortener (pour simplifier le lien) et Tweeter (pour publier le message)
  • BasicShortener.java et MockShortener.java sont 2 implémentations de Shortener
  • SmsTweeter.java et MockTweeter.java sont deux implémentations de Tweeter
  • Shortener.java et Tweeter.java sont les deux interfaces qui définissent les méthodes pour publier et simplifier un lien
  • Tests.java est une classe de test qui lancera notre application avec les implémentations MockShortener et MockTweeter

  • Les modules de configuration

    La configuration de Guice se fait via des objet java appelé Modules. Le developpeur doit créer une classe qui étend AbsractModule de Guice. Lors de l'extention, on peut redefinir la méthode configure() dans laquelle on va pouvoir associer une implémentation à une interface.
    Dans notre cas, nous aurons 2 modules, un pour le lancement normal et un pour les tests. Le module devra ressembler à ça :
    public class TweetModule extends AbstractModule {
       protected void configure() {
          bind(Tweeter.class).to(SmsTweeter.class);
          bind(Shortener.class).to(BasicShortener.class);
    }

    Nous avons déclaré 2 associations : BasicShortener (associé a la classe impl.BasicShortener) et SmsTweeter (associé a impl.smsTweeter).  Pour toute classe qui sera sous la gestion de l'injecteur (que nous verrons plus bas) chaque injection à faire pour ces deux interfaces sera régie par ce module. Dans le cas où l'on souhaiterais avoir plusieurs implémentations pour la même interface dans le code il faudra créer un second module qui sera géré par un second injecteur.


    L'annotation Inject

    Pour pouvoir injecter notre implémentation dans notre classes principale, l'injecteur de Guice va parcourir les classes a la recherche de l'annotation Inject. Cette dernière peut être placée sur un attribut (on injecte directement dans l'attribut), sur un constructeur (on injecte dans les paramètres du constructeurs) ou bien sur une méthode (on injecte dans les paramètres de la méthode).
    Cela nous donne donc pour notre classe TweetClient :
    @Inject
    private Tweeter tweet;
    @Inject
    private Shortener shorten;

    Ici nous avons fais le choix de l'injection sur l'attribut directement. Nous aurions aussi pu avoir annoté le constructeur de TweetClient :
    @Inject
    Public TweetClient(Tweeter tweet, Shortener shorten) {
       this.tweet=tweet;
       this.shorten=shorten;
    }


    L'Injector

    L'Injector est la classe de Guice qui va charger le module et injecter les dépendances à sa charge. Une fois le module chargé, nous allons demander à l'injecteur une instance de notre classe qui a besoin d'injection (ici TweetClient).
    Note : Toutes les sous classes et sous dépendances dont la classe mère a été chargée par un injecteur peuvent bénéfier de l'injection de dépendance. Si on demande a l'injecteur la classe la plus haute de notre application, alors toutes les annotations @Inject seront gérées en une seule fois.
    Cela nous donne donc pour notre Main :
    public static void main(String[] args) {
       Injector injector = Guice.createInjector(new TweetModule());
       TweetClient tweetClient = injector.getInstance(TweetClient.class);
       tweeter.publishWithUrl("mon message de publication", "http://www.monsite.com/truccompliqué&dur_a_retenir=true");
    }

    Ici, en première ligne, nous demande à Guice de créer un injecteur à partir du module TweetModule.
    Ensuite, nous demandons à l'injecteur de nous donner une instance de notre classe mére TweetClient. toutes les annotations trouvées en parcourant TweetClient et ses dépendances directes et indirectes seront résolues par l'injecteur en utilisant les implémentations trouvées dans TweetModule.
    Nous aurons en résultat selon le code mis dans BasicShortener et SmsTweeter notre message avec un lien simplifié vers monsite.com

    Les sources de cet exemple sont disponibles ici