Guide de Google Guice

Introduction

Cet article examinera les principes fondamentaux de Google Guice. Nous examinerons les approches permettant de réaliser des tâches de base d’injection de dépendances (DI) dans Guice.

Nous comparerons et contrasterons également l’approche de Guice avec celles de frameworks DI plus établis comme Spring et Contexts and Dependency Injection (CDI).

Cet article suppose que le lecteur a une compréhension des principes fondamentaux du pattern d’injection de dépendances.

Mise en place

Pour utiliser Google Guice dans votre projet Maven, vous devrez 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 les couvrirons un peu plus tard) ici, ainsi que des modules tiers pour étendre les capacités de Guice (principalement en fournissant une intégration à des frameworks Java plus établis).

Injection de dépendances de base avec Guice

3.1. Notre application type

Nous allons travailler avec un scénario dans lequel nous concevons des classes qui supportent trois moyens de communication dans une entreprise de helpdesk : Courriel, SMS et MI.

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 possède un Communicator que nous utilisons pour effectuer la transmission réelle des messages.

Le point d’entrée de base dans Guice est l’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. Elle introduit également un concept fondamental de Guice : le Module (en utilisant BasicModule dans cet exemple). Le Module est l’unité de base de définition des liaisons (ou câblage, comme on dit dans Spring).

Guice a adopté une approche code-first pour l’injection et la gestion des dépendances, de sorte que vous n’aurez pas à traiter beaucoup de XML en sortie de boîte.

Dans l’exemple ci-dessus, l’arbre de dépendances de Communication sera injecté implicitement en utilisant une fonctionnalité appelée just-in-time binding, à condition que les classes aient le constructeur no-arg par défaut. Cette fonctionnalité est présente dans Guice depuis sa création et n’est disponible dans Spring que depuis la v4.3.

3.2. Guice Bindings

Les bindings sont à Guice ce que le câblage est à Spring. Avec les bindings, vous définissez comment Guice va injecter les dépendances dans une classe.

Un binding est défini 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 named binding. Considérons la déclaration de variable suivante :

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

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

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

Ce binding 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 Jakarta EE, et elles le sont. Elles se trouvent dans le paquet com.google.inject.* – vous devez faire attention à importer à partir du bon paquet lorsque vous utilisez un IDE.

Tip : Bien que nous venions de dire d’utiliser les @Inject et @Named fournis par Guice, il est intéressant de noter que Guice fournit un support pour javax.inject.Inject et javax.inject.Named, parmi d’autres annotations Jakarta EE.

Vous pouvez également injecter une dépendance qui n’a pas de constructeur no-arg par défaut en utilisant le 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));}

Le snippet ci-dessus injectera une instance de Communication en utilisant le constructeur qui prend un argument booléen. Nous fournissons l’argument vrai au constructeur en définissant une liaison non ciblée de la classe Boolean.

Cette liaison non ciblée sera fournie avec empressement à tout constructeur dans la liaison qui accepte un paramètre booléen. Avec cette approche, toutes les dépendances de Communication sont injectées.

Une autre approche du binding spécifique au constructeur est le binding d’instance, où nous fournissons une instance directement dans le binding :

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

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

Dans ce cas, cependant, l’arbre de dépendance de la classe ne sera pas automatiquement câblé. Vous devriez limiter l’utilisation de ce mode là où il n’y a pas d’initialisation lourde ou d’injection de dépendances nécessaire.

Types d’injection de dépendances

Guice supporte les types d’injections standards que vous auriez pu attendre avec le pattern DI. Dans la classe Communicator, nous devons injecter différents types de CommunicationMode.

4.1. Injection de champ

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

Utiliser l’annotation facultative @Named comme qualificateur pour mettre en œuvre une injection ciblée basée sur le nom

4.2. Injection de méthode

Ici nous utilisons une méthode setter pour réaliser l’injection:

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

4.3. Injection par constructeur

Vous pouvez également injecter des dépendances en utilisant un constructeur:

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

4.4. Injections implicites

Guice injectera implicitement certains composants d’usage général comme l’injecteur et une instance de java.util.Logger, entre autres. Vous remarquerez que nous utilisons des loggers tout au long des échantillons mais vous ne trouverez pas de binding réel pour eux.

Scoping dans Guice

Guice supporte les scopes et les mécanismes de scoping auxquels nous nous sommes habitués dans d’autres frameworks DI. Par défaut, Guice fournit 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);

L’in(Scopes.SINGLETON) spécifie que tout champ Communicator avec le @Named(« AnotherCommunicator ») se verra injecter un singleton. Ce singleton est lancé paresseusement par défaut.

5.2. Singleton avide

Maintenant, injectons un singleton avide:

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

L’appel asEagerSingleton() définit le singleton comme instancié de manière avide.

En plus de ces deux scopes, Guice supporte les scopes personnalisés ainsi que les annotations @RequestScoped et @SessionScoped, fournies par Jakarta EE et réservées au web (il n’existe pas de version de ces annotations fournie par Guice).

Programmation orientée aspect dans Guice

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

Etape 1 – Mettre en œuvre le MethodInterceptor de l’AOPAlliance :

public class MessageLogger implements MethodInterceptor { @Inject Logger logger; @Override public Object invoke(MethodInvocation invocation) throws Throwable { Object objectArray = invocation.getArguments(); for (Object object : objectArray) { logger.info("Sending message: " + object.toString()); } return invocation.proceed(); }}

Etape 2 – Définir une annotation Java simple :

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

Etape 3 – Définir un Binding pour un Matcher:

Le Matcher est une classe Guice que nous utilisons pour spécifier 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é ici un Matcher qui appliquera notre intercepteur MessageLogger à toute classe, qui a l’annotation MessageSentLoggable appliquée à ses méthodes.

Etape 4 – Appliquer notre annotation à notre mode de communication et charger notre module

@Override@MessageSentLoggablepublic 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);}

Conclusion

Après avoir examiné les fonctionnalités de base de Guice, nous pouvons voir où l’inspiration de Guice est venue de Spring.

Avec son support de la JSR-330, Guice vise à être un framework DI centré sur l’injection (alors que Spring fournit tout un écosystème pour la commodité de la programmation, pas nécessairement seulement DI), ciblant les développeurs qui veulent une flexibilité DI.

Guice est également très extensible, permettant aux programmeurs d’écrire des plugins portables qui aboutissent à des utilisations flexibles et créatives du framework. Ceci s’ajoute à l’intégration étendue que Guice fournit déjà pour la plupart des frameworks et plateformes populaires comme Servlets, JSF, JPA et OSGi, pour n’en nommer que quelques-uns.

.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *