Guida a Google Guice

Introduzione

Questo articolo esaminerà i fondamenti di Google Guice. Vedremo gli approcci per completare i compiti di base della Dependency Injection (DI) in Guice.

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

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

Setup

Per usare Google Guice nel vostro progetto Maven, avrete bisogno di aggiungere la seguente dipendenza al vostro pom.xml:

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

C’è anche una collezione di estensioni di Guice (le copriremo 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).

Basic Dependency Injection With Guice

3.1. La nostra applicazione di esempio

Lavoreremo con uno scenario in cui progettiamo classi che supportano tre mezzi di comunicazione in un helpdesk: Email, SMS e IM.

Consideriamo 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 Communication è l’unità di base della comunicazione. Un’istanza di questa classe è usata per inviare messaggi attraverso i canali di comunicazione disponibili. Come mostrato sopra, Communication ha un Communicator che usiamo per fare l’effettiva trasmissione dei messaggi.

Il punto di ingresso di base in Guice è l’Injector:

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 Communication. Introduce anche un concetto fondamentale di Guice: il Modulo (usando BasicModule in questo esempio). Il Module è l’unità di base per la definizione dei binding (o wiring, come è noto in Spring).

Guice ha adottato un approccio code-first per l’iniezione e la gestione delle dipendenze, quindi non avrete a che fare con un sacco di XML out-of-the-box.

Nell’esempio sopra, l’albero delle dipendenze di Communication sarà implicitamente iniettato usando una caratteristica chiamata just-in-time binding, a condizione che le classi abbiano il costruttore di default no-arg. Questa è stata una caratteristica di Guice fin dall’inizio e disponibile in Spring solo dalla v4.3.

3.2. Guice Bindings

Il binding sta a Guice come il wiring sta a Spring. Con i binding, si definisce come Guice inietterà le dipendenze in una classe.

Un binding è definito 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 DefaultCommunicatorImpl deve essere iniettata ovunque si trovi una variabile Communicator.

Un’altra incarnazione di questo meccanismo è il named binding. Consideriamo la seguente dichiarazione di variabile:

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

Per questo, avremo la seguente definizione di binding:

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

Questo binding fornirà un’istanza di Communicator a una variabile annotata con l’annotazione @Named(“DefaultCommunicator”).

Si noterà che le annotazioni @Inject e @Named sembrano essere annotazioni di prestito dal CDI di Jakarta EE, e lo sono. Si trovano nel pacchetto com.google.inject.* – si dovrebbe fare attenzione ad importare dal pacchetto giusto quando si usa un IDE.

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

Si può anche iniettare una dipendenza che non ha un costruttore predefinito privo di argomenti usando il binding 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 prende un argomento booleano. Forniamo l’argomento true al costruttore definendo un binding non mirato della classe Boolean.

Questo binding non mirato sarà fornito avidamente a qualsiasi costruttore nel binding che accetta un parametro booleano. Con questo approccio, tutte le dipendenze di Communication sono iniettate.

Un altro approccio al binding specifico del costruttore è il binding di istanza, dove forniamo un’istanza direttamente nel binding:

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

Questo binding fornirà un’istanza della classe Communication ovunque sia dichiarata una variabile Communication.

In questo caso, tuttavia, l’albero delle dipendenze della classe non sarà automaticamente cablato. Si dovrebbe limitare l’uso di questa modalità dove non è necessaria una pesante inizializzazione o iniezione di dipendenze.

Tipi di iniezione di dipendenze

Guice supporta i tipi standard di iniezioni che ci si aspetta dal pattern DI. Nella classe Communicator, abbiamo bisogno di iniettare diversi tipi di CommunicationMode.

4.1. Field Injection

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

Utilizzare l’annotazione opzionale @Named come qualificatore per implementare un’iniezione mirata basata sul nome

4.2. Iniezione di metodi

Qui usiamo un metodo setter per realizzare l’iniezione:

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

4.3. Iniezione del costruttore

Puoi anche iniettare dipendenze usando un costruttore:

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

4.4. Iniezioni implicite

Guice inietterà implicitamente alcuni componenti di uso generale come l’Injector e un’istanza di java.util.Logger, tra gli altri. Noterete che stiamo usando i logger in tutti gli esempi, ma non troverete un vero e proprio binding per loro.

Scoping in Guice

Guice supporta gli scope e i meccanismi di scoping a cui siamo abituati in altri framework DI. Guice fornisce di default una nuova istanza di una dipendenza definita.

5.1. Singleton

Iniettiamo un singleton nella nostra applicazione:

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

L’in(Scopes.SINGLETON) specifica che qualsiasi campo Communicator con il @Named(“AnotherCommunicator”) avrà un singleton iniettato. Questo singleton è avviato pigramente per default.

5.2. Eager Singleton

Ora iniettiamo un eager singleton:

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

La chiamata asEagerSingleton() definisce il singleton come eagerly instantiated.

In aggiunta a questi due ambiti, Guice supporta ambiti personalizzati così come le annotazioni @RequestScoped e @SessionScoped, fornite da Jakarta EE (non ci sono versioni di queste annotazioni fornite da Guice).

Programmazione orientata agli aspetti in Guice

Guice è conforme alle specifiche della AOPAlliance per la programmazione orientata agli aspetti. Possiamo implementare la quintessenza dell’intercettatore di log, che useremo per tracciare l’invio di messaggi nel nostro esempio, in soli quattro passi.

Passo 1 – Implementare il MethodInterceptor della 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(); }}

Passo 2 – Definire una semplice annotazione Java:

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

Step 3 – Definire un Binding per un Matcher:

Matcher è una classe Guice che usiamo per specificare i componenti a cui la nostra annotazione AOP si applica. In questo caso, vogliamo che l’annotazione si applichi alle implementazioni di CommunicationMode:

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

Abbiamo specificato un Matcher che applicherà il nostro intercettore MessageLogger a qualsiasi classe che abbia l’annotazione MessageSentLoggable applicata ai suoi metodi.

Step 4 – Applica la nostra annotazione al nostro Communicationmode e carica il nostro modulo

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

Conclusione

Avendo visto le funzionalità di base di Guice, possiamo vedere dove l’ispirazione per Guice viene da Spring.

Insieme al suo supporto per JSR-330, Guice mira ad essere un framework DI focalizzato sull’iniezione (mentre Spring fornisce un intero ecosistema per la convenienza della programmazione, non necessariamente solo DI), rivolto agli sviluppatori che vogliono flessibilità DI.

Guice è anche altamente estensibile, permettendo ai programmatori di scrivere plugin portatili che risultano in usi flessibili e creativi del framework. Questo è in aggiunta alla vasta integrazione che Guice già fornisce per la maggior parte dei framework e piattaforme popolari come Servlets, JSF, JPA e OSGi, per nominarne alcuni.

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *