Introductie
Dit artikel gaat in op de basis van Google Guice. We zullen kijken naar benaderingen om basis Dependency Injection (DI) taken in Guice uit te voeren.
We zullen ook de Guice aanpak vergelijken met die van meer gevestigde DI frameworks zoals Spring en Contexts and Dependency Injection (CDI).
Dit artikel gaat er van uit dat de lezer de basis van het Dependency Injection patroon kent.
Setup
Om Google Guice in je Maven project te gebruiken, moet je de volgende dependency aan je pom toevoegen.xml:
<dependency> <groupId>com.google.inject</groupId> <artifactId>guice</artifactId> <version>4.1.0</version></dependency>
Er is ook een verzameling van Guice extensies (die we wat later zullen behandelen) hier, evenals modules van derden om de mogelijkheden van Guice uit te breiden (voornamelijk door integratie met meer gevestigde Java frameworks te bieden).
Basic Dependency Injection With Guice
3.1. Our Sample Application
We zullen werken met een scenario waarin we classes ontwerpen die drie communicatiemiddelen in een helpdeskbedrijf ondersteunen: Email, SMS, en IM.
Bedenk de class:
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); }}
Deze Communication class is de basiseenheid van communicatie. Een instantie van deze klasse wordt gebruikt om berichten te verzenden via de beschikbare communicatiekanalen. Zoals hierboven getoond, heeft Communicatie een Communicator die we gebruiken om de eigenlijke berichtverzending te doen.
De basis ingang in Guice is de Injector:
public static void main(String args){ Injector injector = Guice.createInjector(new BasicModule()); Communication comms = injector.getInstance(Communication.class);}
Deze hoofdmethode haalt een instantie op van onze Communicatie klasse. Het introduceert ook een fundamenteel concept van Guice: de Module (met BasicModule in dit voorbeeld). De Module is de basis eenheid voor het definiëren van bindingen (of wiring, zoals het in Spring heet).
Guice heeft een code-first aanpak voor dependency injection en management, dus je zult niet direct met veel XML te maken krijgen.
In het bovenstaande voorbeeld wordt de dependency tree van Communication impliciet geïnjecteerd met behulp van een feature genaamd just-in-time binding, mits de classes de standaard no-arg constructor hebben. Dit is al een feature in Guice sinds het begin en pas beschikbaar in Spring sinds v4.3.
3.2. Guice Bindings
Binding is voor Guice zoals wiring is voor Spring. Met bindingen definieer je hoe Guice afhankelijkheden gaat injecteren in een klasse.
Een binding wordt gedefinieerd in een implementatie van com.google.inject.AbstractModule:
public class BasicModule extends AbstractModule { @Override protected void configure() { bind(Communicator.class).to(DefaultCommunicatorImpl.class); }}
Deze module-implementatie specificeert dat een instantie van DefaultCommunicatorImpl moet worden geïnjecteerd overal waar een Communicator-variabele wordt gevonden.
Een andere incarnatie van dit mechanisme is de named binding. Beschouw de volgende variabele-declaratie:
@Inject @Named("DefaultCommunicator")Communicator communicator;
Daarvoor hebben we de volgende bindingsdefinitie:
@Overrideprotected void configure() { bind(Communicator.class) .annotatedWith(Names.named("DefaultCommunicator")) .to(DefaultCommunicatorImpl.class);}
Deze binding levert een instantie van Communicator aan een variabele die is geannoteerd met de @Named(“DefaultCommunicator”) annotatie.
Je zult zien dat de @Inject en @Named annotaties leen-annotaties lijken te zijn van Jakarta EE’s CDI, en dat zijn ze ook. Ze zitten in het com.google.inject.* pakket – je moet goed opletten dat je uit het juiste pakket importeert als je een IDE gebruikt.
Tip: Hoewel we net zeiden dat je de door Guice geleverde @Inject en @Named moet gebruiken, is het de moeite waard om op te merken dat Guice wel ondersteuning biedt voor javax.inject.Inject en javax.inject.Named, naast andere Jakarta EE annotaties.
Je kunt ook een afhankelijkheid injecteren die geen standaard no-arg constructor heeft met behulp van 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));}
Het bovenstaande fragment injecteert een instantie van Communication met behulp van de constructor die een booleaans argument neemt. We leveren het true argument aan de constructor door een ongerichte binding van de Boolean klasse te definiëren.
Deze ongerichte binding wordt gretig geleverd aan elke constructor in de binding die een boolean parameter accepteert. Met deze aanpak worden alle afhankelijkheden van Communication geïnjecteerd.
Een andere benadering van constructor-specifieke binding is de instance-binding, waarbij we een instantie direct in de binding verstrekken:
public class BasicModule extends AbstractModule { @Override protected void configure() { bind(Communication.class) .toInstance(new Communication(true)); } }
Deze binding verstrekt een instantie van de klasse Communication overal waar een Communication-variabele wordt gedeclareerd.
In dit geval wordt de dependency tree van de klasse echter niet automatisch bekabeld. Je zou het gebruik van deze modus moeten beperken tot gevallen waarin geen zware initialisatie of afhankelijkheidsinjectie nodig is.
Types van afhankelijkheidsinjectie
Guice ondersteunt de standaardtypes van injecties die je gewend bent van het DI-patroon. In de Communicator klasse moeten we verschillende types van CommunicationMode injecteren.
4.1. Veld injectie
@Inject @Named("SMSComms")CommunicationMode smsComms;
Gebruik de optionele @Named annotatie als qualifier om gerichte injectie op basis van de naam te implementeren
4.2. Methode injectie
Hier gebruiken we een setter methode om de injectie te bereiken:
@Injectpublic void setEmailCommunicator(@Named("EmailComms") CommunicationMode emailComms) { this.emailComms = emailComms;}
4.3. Constructor Injection
Je kunt afhankelijkheden ook injecteren met behulp van een constructor:
@Injectpublic Communication(@Named("IMComms") CommunicationMode imComms) { this.imComms= imComms;}
4.4. Impliciete injecties
Guice zal impliciet enkele componenten voor algemene doeleinden injecteren, zoals de Injector en een instantie van java.util.Logger, onder andere. Je zult zien dat we loggers gebruiken in de voorbeelden, maar je zult geen echte binding voor ze vinden.
Scoping in Guice
Guice ondersteunt de scopes en scoping mechanismen die we gewend zijn in andere DI frameworks. Guice levert standaard een nieuwe instantie van een gedefinieerde dependency.
5.1. Singleton
Laten we een singleton in onze applicatie injecteren:
bind(Communicator.class).annotatedWith(Names.named("AnotherCommunicator")) .to(Communicator.class).in(Scopes.SINGLETON);
De in(Scopes.SINGLETON) specificeert dat elk Communicator veld met de @Named(“AnotherCommunicator”) een singleton geïnjecteerd zal krijgen. Deze singleton wordt standaard lui geïnitieerd.
5.2. Eager Singleton
Nu gaan we een eager singleton injecteren:
bind(Communicator.class).annotatedWith(Names.named("AnotherCommunicator")) .to(Communicator.class) .asEagerSingleton();
De aanroep asEagerSingleton() definieert de singleton als eager geïnstantieerd.
Naast deze twee scopes, ondersteunt Guice ook aangepaste scopes en de web-only @RequestScoped en @SessionScoped annotaties, geleverd door Jakarta EE (er zijn geen Guice-versies van deze annotaties).
Aspect Oriented Programming in Guice
Guice voldoet aan de specificaties van de AOPAlliance voor aspect-georiënteerd programmeren. We kunnen de quintessentiële logging interceptor, die we zullen gebruiken om het versturen van berichten in ons voorbeeld te volgen, in slechts vier stappen implementeren.
Stap 1 – Implementeer de MethodInterceptor van de 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(); }}
Stap 2 – Definieer een Plain Java Annotation:
@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)public @interface MessageSentLoggable {}
Stap 3 – Definieer een Binding voor een Matcher:
Matcher is een Guice class die we gebruiken om de componenten te specificeren waarop onze AOP annotatie van toepassing zal zijn. In dit geval willen we dat de annotatie van toepassing is op implementaties van CommunicationMode:
public class AOPModule extends AbstractModule { @Override protected void configure() { bindInterceptor( Matchers.any(), Matchers.annotatedWith(MessageSentLoggable.class), new MessageLogger() ); }}
We hebben hier een Matcher gespecificeerd die onze MessageLogger interceptor zal toepassen op elke klasse, die de MessageSentLoggable annotatie op haar methoden heeft toegepast.
Stap 4 – Pas onze annotatie toe op onze communicatiemode en laad onze 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);}
Conclusie
Nu we de basis Guice functionaliteit hebben bekeken, kunnen we zien waar de inspiratie voor Guice vandaan kwam bij Spring.
Guice is, samen met de ondersteuning voor JSR-330, bedoeld als een injectie-gericht DI framework (terwijl Spring een heel ecosysteem biedt voor programmeergemak, niet noodzakelijkerwijs alleen DI), gericht op ontwikkelaars die DI flexibiliteit willen.
Guice is ook zeer uitbreidbaar, waardoor programmeurs portable plugins kunnen schrijven die resulteren in flexibel en creatief gebruik van het framework. Dit is in aanvulling op de uitgebreide integratie die Guice al biedt voor de meeste populaire frameworks en platforms zoals Servlets, JSF, JPA, en OSGi, om er een paar te noemen.