Leitfaden zu Google Guice

Einführung

In diesem Artikel werden wir die Grundlagen von Google Guice untersuchen. Wir werden uns Ansätze ansehen, um grundlegende Dependency Injection (DI)-Aufgaben in Guice zu erledigen.

Wir werden auch den Guice-Ansatz mit denen etablierterer DI-Frameworks wie Spring und Contexts and Dependency Injection (CDI) vergleichen und gegenüberstellen.

Dieser Artikel setzt voraus, dass der Leser ein Verständnis für die Grundlagen des Dependency Injection-Musters hat.

Setup

Um Google Guice in Ihrem Maven-Projekt verwenden zu können, müssen Sie die folgende Abhängigkeit zu Ihrem pom hinzufügen.xml:

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

Es gibt auch eine Sammlung von Guice-Erweiterungen (wir werden diese etwas später behandeln), sowie Module von Drittanbietern, um die Fähigkeiten von Guice zu erweitern (hauptsächlich durch die Integration in etabliertere Java-Frameworks).

Basic Dependency Injection With Guice

3.1. Unsere Beispielanwendung

Wir werden mit einem Szenario arbeiten, in dem wir Klassen entwerfen, die drei Kommunikationsmittel in einem Helpdesk-Unternehmen unterstützen: E-Mail, SMS und IM.

Betrachten Sie die Klasse:

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

Diese Kommunikationsklasse ist die Grundeinheit der Kommunikation. Eine Instanz dieser Klasse wird verwendet, um Nachrichten über die verfügbaren Kommunikationskanäle zu versenden. Wie oben gezeigt, hat Communication einen Communicator, mit dem wir die eigentliche Nachrichtenübertragung durchführen.

Der grundlegende Einstiegspunkt in Guice ist der Injector:

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

Diese Hauptmethode ruft eine Instanz unserer Communication-Klasse ab. Sie führt auch ein grundlegendes Konzept von Guice ein: das Modul (in diesem Beispiel BasicModule). Das Modul ist die grundlegende Einheit für die Definition von Bindungen (oder Verdrahtungen, wie es in Spring genannt wird).

Guice hat einen Code-first-Ansatz für die Injektion und Verwaltung von Abhängigkeiten gewählt, sodass Sie nicht mit viel XML out-of-the-box zu tun haben werden.

Im obigen Beispiel wird der Abhängigkeitsbaum der Klasse Communication implizit mit Hilfe einer Funktion namens Just-in-Time-Bindung injiziert, vorausgesetzt, die Klassen haben den standardmäßigen no-arg-Konstruktor. Dies ist ein Feature, das in Guice von Anfang an vorhanden war und in Spring erst seit v4.3.

3.2. Guice Bindings

Binding ist für Guice das, was Verdrahtung für Spring ist. Mit Bindings wird definiert, wie Guice Abhängigkeiten in eine Klasse injizieren soll.

Ein Binding wird in einer Implementierung von com.google.inject.AbstractModule definiert:

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

Diese Modulimplementierung legt fest, dass eine Instanz von DefaultCommunicatorImpl überall dort injiziert werden soll, wo eine Communicator-Variable gefunden wird.

Eine weitere Ausprägung dieses Mechanismus ist die benannte Bindung. Betrachten Sie die folgende Variablendeklaration:

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

Dazu haben wir die folgende Bindungsdefinition:

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

Diese Bindung wird eine Instanz von Communicator an eine Variable übergeben, die mit der @Named(„DefaultCommunicator“) Annotation annotiert ist.

Sie werden feststellen, dass die @Inject- und @Named-Annotationen Leihannotationen aus dem CDI von Jakarta EE zu sein scheinen, und das sind sie auch. Sie befinden sich im Paket com.google.inject.* – Sie sollten darauf achten, aus dem richtigen Paket zu importieren, wenn Sie eine IDE verwenden.

Tipp: Obwohl wir gerade gesagt haben, dass Sie die von Guice bereitgestellten @Inject und @Named verwenden sollen, ist es sinnvoll zu beachten, dass Guice Unterstützung für javax.inject.Inject und javax.inject.Named, neben anderen Jakarta EE Annotationen.

Sie können auch eine Abhängigkeit injizieren, die keinen standardmäßigen No-Arg-Konstruktor hat, indem Sie die Konstruktorbindung verwenden:

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

Der obige Ausschnitt injiziert eine Instanz von Communication, indem er den Konstruktor verwendet, der ein boolesches Argument annimmt. Wir übergeben das Argument true an den Konstruktor, indem wir eine nicht zielgerichtete Bindung der Klasse Boolean definieren.

Diese nicht zielgerichtete Bindung wird jedem Konstruktor in der Bindung, der einen booleschen Parameter akzeptiert, eifrig übergeben. Mit diesem Ansatz werden alle Abhängigkeiten von Communication injiziert.

Ein anderer Ansatz zur konstruktorspezifischen Bindung ist die Instanzbindung, bei der wir eine Instanz direkt in der Bindung bereitstellen:

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

Diese Bindung stellt eine Instanz der Klasse Communication bereit, wo immer eine Communication-Variable deklariert wird.

In diesem Fall wird der Abhängigkeitsbaum der Klasse jedoch nicht automatisch verdrahtet. Sie sollten diesen Modus nur dort verwenden, wo keine umfangreiche Initialisierung oder Dependency Injection notwendig ist.

Typen der Dependency Injection

Guice unterstützt die Standardtypen von Injektionen, die Sie vom DI-Muster gewohnt sind. In der Communicator-Klasse müssen wir verschiedene Typen von CommunicationMode injizieren.

4.1. Feldinjektion

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

Verwenden Sie die optionale @Named-Annotation als Qualifier, um eine gezielte Injektion basierend auf dem Namen zu implementieren

4.2. Methodeninjektion

Hier verwenden wir eine Setter-Methode, um die Injektion zu erreichen:

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

4.3. Konstruktor-Injektion

Sie können Abhängigkeiten auch über einen Konstruktor injizieren:

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

4.4. Implizite Injektionen

Guice injiziert implizit einige Allzweckkomponenten wie den Injector und eine Instanz von java.util.Logger. Sie werden bemerken, dass wir in den Beispielen Logger verwenden, aber Sie werden keine tatsächliche Bindung für sie finden.

Scoping in Guice

Guice unterstützt die Scopes und Scoping-Mechanismen, an die wir uns in anderen DI-Frameworks gewöhnt haben. Guice stellt standardmäßig eine neue Instanz einer definierten Abhängigkeit bereit.

5.1. Singleton

Lassen Sie uns ein Singleton in unsere Anwendung injizieren:

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

Das in(Scopes.SINGLETON) legt fest, dass jedes Communicator-Feld mit dem @Named(„AnotherCommunicator“) ein Singleton injiziert bekommt. Dieses Singleton wird standardmäßig „lazily“ initiiert.

5.2. Eager-Singleton

Nun wollen wir ein eager-Singleton injizieren:

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

Der asEagerSingleton()-Aufruf definiert das Singleton als eager instanziert.

Zusätzlich zu diesen beiden Scopes unterstützt Guice auch benutzerdefinierte Scopes sowie die nur im Web verfügbaren Annotationen @RequestScoped und @SessionScoped, die von Jakarta EE bereitgestellt werden (es gibt keine von Guice bereitgestellten Versionen dieser Annotationen).

Aspektorientierte Programmierung in Guice

Guice ist konform mit den Spezifikationen der AOPAlliance für aspektorientierte Programmierung. Wir können den Quintessenz-Logging-Interceptor, den wir in unserem Beispiel zum Verfolgen des Nachrichtenversands verwenden werden, in nur vier Schritten implementieren.

Schritt 1 – Implementieren Sie den MethodInterceptor der 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(); }}

Schritt 2 – Definieren Sie eine Plain Java Annotation:

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

Schritt 3 – Definieren Sie eine Bindung für einen Matcher:

Matcher ist eine Guice-Klasse, mit der wir die Komponenten festlegen, auf die unsere AOP-Annotation angewendet werden soll. In diesem Fall wollen wir, dass die Annotation auf Implementierungen von CommunicationMode angewendet wird:

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

Wir haben hier einen Matcher spezifiziert, der unseren MessageLogger-Interceptor auf jede Klasse anwendet, die die Annotation MessageSentLoggable auf ihre Methoden angewendet hat.

Schritt 4 – Wenden Sie unsere Annotation auf unseren Kommunikationsmodus an und laden Sie unser Modul

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

Abschluss

Nachdem wir uns die grundlegende Guice-Funktionalität angesehen haben, können wir erkennen, woher die Inspiration für Guice von Spring kam.

Zusammen mit der Unterstützung für JSR-330 zielt Guice darauf ab, ein auf Injektion fokussiertes DI-Framework zu sein (wohingegen Spring ein ganzes Ökosystem für Programmierkomfort bietet, nicht notwendigerweise nur DI), das auf Entwickler abzielt, die DI-Flexibilität wünschen.

Guice ist außerdem in hohem Maße erweiterbar, was es Programmierern erlaubt, portable Plugins zu schreiben, die eine flexible und kreative Nutzung des Frameworks ermöglichen. Dies ist zusätzlich zu der umfangreichen Integration, die Guice bereits für die meisten populären Frameworks und Plattformen wie Servlets, JSF, JPA und OSGi bietet, um nur einige zu nennen.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.