Guide de Google Guice

1. Introduction

Cet article examinera les principes fondamentaux de Google Guice . Nous examinerons les méthodes permettant d’accomplir les tâches de base relatives à l’injection de dépendance dans Guice.

Nous comparerons également l’approche Guice à celle d’instruments de DI plus établis, tels que Spring et Contexts and Dependency Injection (CDI).

Cet article suppose que le lecteur comprenne les principes fondamentaux du lien:/inversion-control-and-dependency-injection-in-spring[modèle d’injection de dépendance].

2. Installer

Pour utiliser Google Guice dans votre projet Maven, vous devez ajouter la dépendance suivante à votre pom.xml :

<dependency>
    <groupId>com.google.inject</groupId>
    <artifactId>guice</artifactId>
    <version>4.1.0</version>
</dependency>

Il existe également une collection d’extensions Guice (nous en parlerons un peu plus tard) https://search.maven.org/classic/#search%7Cga%7C1%7Cg%3A%22com.google.inject.extensions%22% 20AND% 20V% 3A% 224.1.0% 22[ici], ainsi que as des modules tiers pour étendre les fonctionnalités de Guice (principalement en fournissant des à des frameworks Java plus établis).

3. Injection de dépendance de base avec Guice

3.1. Notre exemple d’application

Nous allons travailler avec un scénario dans lequel nous concevons des classes prenant en charge trois moyens de communication dans une entreprise de service d’assistance: messagerie électronique, SMS et messagerie instantanée.

Considérez la classe:

public class Communication {

    @Inject
    private Logger logger;

    @Inject
    private Communicator communicator;

    public Communication(Boolean keepRecords) {
        if (keepRecords) {
            System.out.println("Message logging enabled");
        }
    }

    public boolean sendMessage(String message) {
        return communicator.sendMessage(message);
    }

}

Cette classe de communication est l’unité de base de la communication. Une instance de cette classe est utilisée pour envoyer des messages via les canaux de communication disponibles. Comme indiqué ci-dessus, Communication a un Communicator que nous utilisons pour transmettre le message.

Le point d’entrée de base dans Guice est le Injector:

public static void main(String[]args){
    Injector injector = Guice.createInjector(new BasicModule());
    Communication comms = injector.getInstance(Communication.class);
}

Cette méthode principale récupère une instance de notre classe Communication . Il introduit également un concept fondamental de Guice: le Module (en utilisant BasicModule dans cet exemple). Le Module est l’unité de base de la définition des liaisons (ou du câblage, comme il est connu dans Spring).

  • Guice a adopté une approche basée sur le code pour l’injection et la gestion des dépendances ** afin que vous ne traitiez pas beaucoup de XML immédiatement.

Dans l’exemple ci-dessus, l’arbre de dépendance de Communication sera implicitement injecté à l’aide d’une fonctionnalité appelée just-in-time binding , à condition que les classes disposent du constructeur no-arg par défaut. C’est une fonctionnalité de Guice depuis sa création et disponible seulement au printemps depuis la v4.3.

3.2. Manchettes Guice

La reliure est à Guice comme le câblage est à Spring. Avec les liaisons, vous définissez comment Guice va injecter des dépendances dans une classe.

Une liaison est définie dans une implémentation de com.google.inject.AbstractModule :

public class BasicModule extends AbstractModule {

    @Override
    protected void configure() {
        bind(Communicator.class).to(DefaultCommunicatorImpl.class);
    }
}

Cette implémentation de module spécifie qu’une instance de DefaultCommunicatorImpl doit être injectée partout où une variable Communicator est trouvée.

  • Une autre incarnation de ce mécanisme est le binding ** . Considérons la déclaration de variable suivante:

@Inject @Named("DefaultCommunicator")
Communicator communicator;

Pour cela, nous aurons la définition de liaison suivante:

@Override
protected void configure() {
    bind(Communicator.class)
      .annotatedWith(Names.named("DefaultCommunicator"))
      .to(Communicator.class);
}

Cette liaison fournira une instance de Communicator à une variable annotée avec l’annotation @ Named («DefaultCommunicator») .

Vous remarquerez que les annotations @ Inject et @ Named semblent être des annotations de prêt du CDI de JavaEE, et elles le sont. Ils se trouvent dans le package com.google.inject. ** - vous devez faire attention à importer depuis le bon package lorsque vous utilisez un environnement de développement intégré.

Conseil: Bien que nous venions de dire d’utiliser les fichiers @ Inject et @ Named fournis par Guice, il est intéressant de noter que Guice prend en charge javax.inject.Inject et __javax.inject.Named, entre autres JavaEE annotations.

  • Vous pouvez également injecter une dépendance qui n’a pas de constructeur No-Arg par défaut en utilisant constructor binding ** :

public class BasicModule extends AbstractModule {

    @Override
    protected void configure() {
        bind(Boolean.class).toInstance(true);
        bind(Communication.class).toConstructor(
          Communication.class.getConstructor(Boolean.TYPE));
}

L’extrait ci-dessus va injecter une instance de Communication en utilisant le constructeur qui prend un argument boolean . Nous fournissons l’argument true au constructeur en définissant un untargeted binding de la classe Boolean .

Cette liaison ciblée sera fournie à tout constructeur de la liaison qui accepte un paramètre boolean . Avec cette approche, toutes les dépendances de Communication__ sont injectées.

  • Une autre approche de la liaison spécifique au constructeur est la instance binding ** , où nous fournissons une instance directement dans la liaison:

public class BasicModule extends AbstractModule {

    @Override
    protected void configure() {
        bind(Communication.class)
          .toInstance(new Communication(true));
    }
}

Cette liaison fournira une instance de la classe Communication partout où une variable Communication est déclarée.

Dans ce cas, toutefois, l’arbre de dépendance de la classe ne sera pas automatiquement câblé. Vous devez limiter l’utilisation de ce mode là où aucune initialisation lourde ou injection de dépendance n’est nécessaire.

4. Types d’injection de dépendance

Guice prend en charge les types d’injections standard auxquels vous vous attendiez avec le modèle DI. Dans la classe Communicator , nous devons injecter différents types de CommunicationMode .

4.1. Injection de champ

@Inject @Named("SMSComms")
CommunicationMode smsComms;

Utilisez l’annotation facultative @ Named comme qualificatif pour implémenter une injection ciblée en fonction du nom.

4.2. Méthode d’injection

Ici, nous utilisons une méthode de réglage pour réaliser l’injection:

@Inject
public void setEmailCommunicator(@Named("EmailComms") CommunicationMode emailComms) {
    this.emailComms = emailComms;
}

4.3. Injection constructeur

Vous pouvez également injecter des dépendances à l’aide d’un constructeur:

@Inject
public Communication(@Named("IMComms") CommunicationMode imComms) {
    this.imComms= imComms;
}

4.4. Injections implicites

Guice injectera implicitement des composants à usage général tels que Injector et une instance de java.util.Logger , entre autres. Vous remarquerez que nous utilisons des enregistreurs tout au long des exemples, mais vous ne trouverez pas de lien réel pour eux.

5. Portée à Guice

Guice prend en charge les champs d’application et les mécanismes de champ d’application auxquels nous sommes habitués dans d’autres cadres DI. Guice fournit par défaut une nouvelle instance d’une dépendance définie.

5.1. Singleton

Injectons un singleton dans notre application:

bind(Communicator.class).annotatedWith(Names.named("AnotherCommunicator"))
  .to(Communicator.class).in(Scopes.SINGLETON);

Le in (Scopes.SINGLETON) spécifie que tout champ Communicator avec le @ Named («AnotherCommunicator») obtiendra un singleton injecté. Ce singleton est initié paresseusement par défaut.

5.2. Singleton impatient

Injectons maintenant un singleton impatient:

bind(Communicator.class).annotatedWith(Names.named("AnotherCommunicator"))
  .to(Communicator.class)
  .asEagerSingleton();

L’appel asEagerSingleton () définit le singleton comme instancié avec impatience.

En plus de ces deux portées, Guice prend en charge les portées personnalisées ainsi que les annotations @ RequestScoped et @ SessionScoped réservées au Web, fournies par JavaEE (aucune version de ces annotations fournie par Guice).

6. Programmation orientée aspect dans Guice

Guice est conforme aux spécifications de la programmation orientée aspect de l’AOPAlliance. Nous pouvons implémenter l’intercepteur de journalisation par excellence, que nous utiliserons pour suivre l’envoi de messages dans notre exemple, en seulement quatre étapes.

Étape 1 - Implémentez le fichier http://aopalliance.sourceforge.net/doc/org/aopalliance/intercept/MethodInterceptor.html d’AppAlliance.html[MethodInterceptor] :

public class LoggingInterceptor implements MethodInterceptor {

    @Inject
    Logger logger;

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        Object[]objectArray = invocation.getArguments();
        int i = 0;
        for (Object object : objectArray) {
            logger.info("Sending message: " + object.toString());
        }
        return invocation.proceed();
    }
}

Étape 2 - Définissez une annotation Java simple :

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MessageSentLoggable {
}

Étape 3 - Définissez une liaison pour un Matcher :

Matcher est une classe de Guice que nous utilisons spécifions les composants auxquels notre annotation AOP s’appliquera. Dans ce cas, nous voulons que l’annotation s’applique aux implémentations de CommunicationMode:

public class AOPModule extends AbstractModule {

    @Override
    protected void configure() {
        bindInterceptor(
            Matchers.any(),
            Matchers.annotatedWith(MessageSentLoggable.class),
            new MessageLogger()
        );
    }
}

Nous avons spécifié un Matcher qui appliquera notre intercepteur MessageLogger à any class, auquel l’annotation MessageSentLoggable est appliquée à ses méthodes.

Étape 4 - Appliquez notre annotation à notre CommunicationMode et chargez notre module

@Override
@MessageSentLoggable
public boolean sendMessage(String message) {
    logger.info("SMS message sent");
    return true;
}

public static void main(String[]args) {
    Injector injector = Guice.createInjector(new BasicModule(), new AOPModule());
    Communication comms = injector.getInstance(Communication.class);
}

7. Conclusion

Après avoir examiné les fonctionnalités de base de Guice, nous pouvons voir d’où vient l’inspiration pour Guice du printemps.

Parallèlement à son support pour JSR-330 , Guice vise à être un cadre DI orienté sur l’injection (alors que Spring fournit un écosystème complet pour la commodité de la programmation, pas seulement la DI ), destinés aux développeurs qui recherchent la flexibilité des DI.

Guice est également extrêmement extensible , permettant aux programmeurs d’écrire des plug-ins portables qui permettent une utilisation flexible et créative du framework. Ceci s’ajoute à l’intégration poussée déjà fournie par Guice pour les infrastructures et plates-formes les plus courantes telles que Servlets, JSF, JPA et OSGi, pour ne citer que quelques exemples.

Vous pouvez trouver tout le code source utilisé dans ce tutoriel dans notre projet GitHub .