Guida a Google Guice

1. Introduzione

Questo articolo esaminerà i fondamenti di Google Guice . Esamineremo gli approcci per completare le attività di base di Dependency Injection (DI) in Guice.

Confronteremo e confronteremo anche l'approccio di Guice con quelli di framework DI più consolidati come Spring and Contexts e Dependency Injection (CDI).

Questo articolo presume che il lettore abbia una comprensione dei fondamenti del pattern Dependency Injection.

2. Configurazione

Per utilizzare Google Guice nel tuo progetto Maven, dovrai aggiungere la seguente dipendenza al tuo pom.xml :

 com.google.inject guice 4.1.0  

C'è anche una raccolta di estensioni Guice (le tratteremo un po 'più avanti) qui, così come moduli di terze parti per estendere le capacità di Guice (principalmente fornendo integrazione a framework Java più consolidati).

3. Iniezione di dipendenza di base con Guice

3.1. La nostra applicazione di esempio

Lavoreremo con uno scenario in cui progettiamo classi che supportano tre mezzi di comunicazione in un'attività di helpdesk: e-mail, SMS e IM.

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

Questa classe di comunicazione è l'unità di base della comunicazione. Un'istanza di questa classe viene utilizzata per inviare messaggi tramite i canali di comunicazione disponibili. Come mostrato sopra, Communication dispone di un Comunicatore che utilizziamo per la trasmissione effettiva del messaggio.

Il punto di ingresso di base in Guice è l' iniettore:

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

Questo metodo principale recupera un'istanza della nostra classe di comunicazione . Inoltre introduce un concetto fondamentale di Guice: il Modulo (usando BasicModule in questo esempio). Il Modulo è l'unità di base della definizione dei legami (o cablaggio, come è noto in primavera).

Guice ha adottato un approccio code-first per l'inserimento e la gestione delle dipendenze in modo da non dover gestire un sacco di XML out-of-the-box.

Nell'esempio precedente, l'albero delle dipendenze di Communication verrà iniettato implicitamente utilizzando una funzionalità chiamata just-in-time binding , a condizione che le classi abbiano il costruttore no-arg predefinito. Questa è stata una funzionalità di Guice sin dall'inizio e disponibile solo in primavera dalla v4.3.

3.2. Attacchi Guice

Il legame è per Guice come il cablaggio è per Spring. Con i binding, definisci come Guice inserirà le dipendenze in una classe.

Un'associazione è definita in un'implementazione di com.google.inject.AbstractModule :

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

Questa implementazione del modulo specifica che un'istanza di Default CommunicatorImpl deve essere iniettata ogni volta che viene trovata una variabile di Communicator .

Un'altra incarnazione di questo meccanismo è l' associazione denominata . Considera la seguente dichiarazione di variabile:

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

Per questo, avremo la seguente definizione vincolante:

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

Questa associazione fornirà un'istanza di Communicator a una variabile annotata con l'annotazione @Named ("DefaultCommunicator") .

Noterai che le annotazioni @Inject e @Named sembrano essere annotazioni di prestito dal CDI di Jakarta EE, e lo sono. Sono nel pacchetto com.google.inject. * : Dovresti fare attenzione a importare dal pacchetto giusto quando usi un IDE.

Suggerimento: anche se abbiamo appena detto di usare @Inject e @Named forniti da Guice , vale la pena notare che Guice fornisce supporto per javax.inject.Inject e javax.inject.Named, tra le altre annotazioni Jakarta EE.

Puoi anche iniettare una dipendenza che non ha un costruttore no-arg predefinito usando l' associazione del costruttore :

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

Lo snippet sopra inietterà un'istanza di Communication usando il costruttore che accetta un argomento booleano . Forniamo il vero argomento al costruttore definendo un'associazione non mirata della classe booleana .

Questa associazione non mirata verrà fornita con entusiasmo a qualsiasi costruttore dell'associazione che accetti un parametro booleano . Con questo approccio, vengono iniettate tutte le dipendenze della comunicazione .

Un altro approccio all'associazione specifica del costruttore è l' associazione dell'istanza , in cui forniamo un'istanza direttamente nell'associazione:

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

Questa associazione fornirà un'istanza della classe Communication ogniqualvolta viene dichiarata una variabile Communication .

In questo caso, tuttavia, l'albero delle dipendenze della classe non verrà cablato automaticamente. È necessario limitare l'uso di questa modalità laddove non è necessaria alcuna inizializzazione pesante o inserimento di dipendenze.

4. Tipi di inserimento delle dipendenze

Guice supporta i tipi standard di iniezioni che ti aspetteresti con il pattern DI. Nella classe Communicator , dobbiamo inserire diversi tipi di CommunicationMode .

4.1. Iniezione sul campo

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

Utilizza l' annotazione @Named facoltativa come qualificatore per implementare l'iniezione mirata in base al nome

4.2. Metodo di iniezione

Qui usiamo un metodo setter per ottenere l'iniezione:

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

4.3. Iniezione del costruttore

Puoi anche iniettare dipendenze usando un costruttore:

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

4.4. Iniezioni implicite

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.

Insieme al supporto per JSR-330, Guice mira a essere un framework DI focalizzato sull'iniezione (mentre Spring fornisce un intero ecosistema per comodità di programmazione non necessariamente solo DI), destinato agli sviluppatori che desiderano flessibilità DI.

Guice è anche altamente estensibile, consentendo ai programmatori di scrivere plugin portatili che si traducono in usi flessibili e creativi del framework. Questo è in aggiunta all'ampia integrazione che Guice già fornisce per i framework e le piattaforme più popolari come Servlet, JSF, JPA e OSGi, solo per citarne alcuni.

Puoi trovare tutto il codice sorgente utilizzato in questo tutorial nel nostro progetto GitHub.