Guide de Google Guice

1. Introduction

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

Nous comparerons et opposerons également l'approche Guice à celles des frameworks DI plus établis comme Spring et Context and Dependency Injection (CDI).

Cet article suppose que le lecteur comprend les principes fondamentaux du modèle d'injection de dépendance.

2. Configuration

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

 com.google.inject guice 4.1.0  

Il existe également une collection d'extensions Guice (nous les couvrirons un peu plus tard), ainsi que des modules tiers pour étendre les capacités de Guice (principalement en fournissant une intégration à des frameworks Java plus établis).

3. Injection de dépendance de base avec Guice

3.1. Notre exemple d'application

Nous travaillerons avec un scénario dans lequel nous concevrons des classes prenant en charge trois moyens de communication dans une entreprise de helpdesk: Email, SMS et IM.

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 dispose d'un Communicator que nous utilisons pour effectuer la transmission réelle du message.

Le point d'entrée de base dans Guice est l' injecteur:

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 on l'appelle dans Spring).

Guice a adopté une approche basée sur le code pour l'injection et la gestion des dépendances afin que vous n'ayez pas à gérer beaucoup de XML prêt à l'emploi.

Dans l'exemple ci-dessus, l'arborescence de dépendances de Communication sera implicitement injectée à l'aide d'une fonctionnalité appelée liaison juste à temps , à condition que les classes aient le constructeur par défaut no-arg. C'est une fonctionnalité de Guice depuis sa création et uniquement disponible au printemps depuis la v4.3.

3.2. Liaisons de guice

La liaison 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 Default CommunicatorImpl doit être injectée partout où une variable Communicator est trouvée.

Une autre incarnation de ce mécanisme est la liaison nommée . Considérez 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 Jakarta EE, et elles le sont. Ils se trouvent dans le package com.google.inject. * - vous devez faire attention à importer à partir du bon package lorsque vous utilisez un IDE.

Astuce: Bien que nous venons de dire d'utiliser les @Inject et @Named fournis par Guice , il convient de noter que Guice fournit un support pour javax.inject.Inject et javax.inject.Named, entre autres annotations Jakarta EE.

Vous pouvez également injecter une dépendance qui n'a pas de constructeur sans argument par défaut à l'aide de la liaison de constructeur :

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 injectera une instance de Communication en utilisant le constructeur qui prend un argument booléen . Nous fournissons le vrai argument au constructeur en définissant une liaison non ciblée de la classe booléenne .

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

Une autre approche de la liaison spécifique au constructeur est la liaison d'instance , 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, cependant, l'arborescence de dépendances de la classe ne sera pas automatiquement câblée. Vous devez limiter l'utilisation de ce mode là où aucune initialisation lourde ou injection de dépendances n'est nécessaire.

4. Types d'injection de dépendance

Guice prend en charge les types d'injections standard auxquels vous vous attendriez 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 l'injection ciblée basée sur le nom

4.2. Injection de méthode

Ici, nous utilisons une méthode de setter pour réaliser l'injection:

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

4.3. Injection de 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 will implicitly inject some general purpose components like the Injector and an instance of java.util.Logger, among others. You'll notice we are using loggers all through the samples but you won't find an actual binding for them.

5. Scoping in Guice

Guice supports the scopes and scoping mechanisms we have grown used to in other DI frameworks. Guice defaults to providing a new instance of a defined dependency.

5.1. Singleton

Let's inject a singleton into our application:

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

The in(Scopes.SINGLETON) specifies that any Communicator field with the @Named(“AnotherCommunicator”) will get a singleton injected. This singleton is lazily initiated by default.

5.2. Eager Singleton

Now, let's inject an eager singleton:

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

The asEagerSingleton() call defines the singleton as eagerly instantiated.

In addition to these two scopes, Guice supports custom scopes as well as the web-only @RequestScoped and @SessionScoped annotations, supplied by Jakarta EE (there are no Guice-supplied versions of those annotations).

6. Aspect Oriented Programming in Guice

Guice is compliant with the AOPAlliance's specifications for aspect-oriented programming. We can implement the quintessential logging interceptor, which we will use to track message sending in our example, in only four steps.

Step 1 – Implement the AOPAlliance's MethodInterceptor:

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(); } } 

Step 2 – Define a Plain Java Annotation:

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

Step 3 – Define a Binding for a Matcher:

Matcher is a Guice class that we use do specify the components that our AOP annotation will apply to. In this case, we want the annotation to apply to implementations of CommunicationMode:

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

We have specified a Matcher here that will apply our MessageLogger interceptor to any class, that has the MessageSentLoggable annotation applied to its methods.

Step 4 – Apply Our Annotation to Our Communicationmode and Load Our 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

Having looked at basic Guice functionality, we can see where the inspiration for Guice came from Spring.

Parallèlement à sa prise en charge de JSR-330, Guice vise à être un cadre de DI axé sur l'injection (alors que Spring fournit un écosystème complet pour la commodité de la programmation, pas nécessairement seulement DI), destiné aux développeurs qui veulent de la flexibilité DI.

Guice est également hautement extensible, permettant aux programmeurs d'écrire des plugins portables qui se traduisent par des utilisations flexibles et créatives du framework. Ceci s'ajoute à l'intégration étendue que Guice fournit déjà pour les frameworks et plates-formes les plus populaires tels que Servlets, JSF, JPA et OSGi, pour n'en nommer que quelques-uns.

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