En praktisk session med Google Guice

For et par måneder siden skrev jeg en artikel, der forklarede afhængighedsinjektion. Jeg havde nævnt en opfølgningsartikel med en praktisk session med Google Guice. Mens jeg er skuffet over at være så sent på at skrive dette, er en del af mig glad for, at jeg var i stand til at skrive en anden artikel.

Denne artikel antager, at du er fortrolig med, hvad afhængighedsinjektion er. Jeg vil anbefale dig at kigge igennem min tidligere artikel, da vi bygger videre på de eksempler, vi brugte der. Hvis du hører udtrykket for første gang, vil det være det værd. Hvis du er fortrolig med det, tager det ikke meget tid at læse det :)

Hvis du ikke har arbejdet meget med Guice, så tjek det på GitHub her.

Vi bliver nødt til at oprette et par ting, inden vi starter

  1. JDK : Vi bruger Java til denne opgave. Så du bliver nødt til at have en fungerende JDK for at kunne køre Java-kode på din computer. For at kontrollere, om det allerede er installeret, skal du køre 'java -version' på kommandolinjen. Hvis versionen er 1.6 eller nyere, er vi gode. Bare en note: Jeg tror ikke, det ville give meget mening at prøve dette, hvis du ikke har erfaring med Java .
  2. Maven : Vi bruger maven som et byggeværktøj. For at installere maven skal du følge instruktionerne her //maven.apache.org/install.html (Ganske let). For at kontrollere, om du allerede har maven, skal du køre 'mvn -v' på kommandolinjen.
  3. git (valgfrit): //www.linode.com/docs/development/version-control/how-to-install-git-on-linux-mac-and-windows/
  4. klon hænderne på arkivet (FreshGuice) : Kør kommandoer nævnt nedenfor
cd folder/to/clone-into/ git clone //github.com/sankalpbhatia/FreshGuice.git

Bindinger og bindende kommentarer

Vi er klar nu. Lad mig starte med at introducere to vigtige udtryk i Guice-rammen: Bindinger og bindende kommentarer.

Bindinger: At være kernekonceptet i Guice betyder bogstaveligt en aftale eller et løfte, der involverer en forpligtelse, der ikke kan brydes. Lad os kortlægge det i sammenhæng med afhængighedsinjektion. Når vi får Guice til at binde en instans med en klasse, indgår vi en aftale med Guice om, at "Når jeg beder om en forekomst af X.java, giv mig denne instans". Og denne aftale kan ikke brydes.

Bindende kommentarer: Lejlighedsvis vil du have flere bindinger til den samme type. Annotationen og (klasse) -typen identificerer entydigt en binding. For eksempel kan du i nogle tilfælde have brug for to separate forekomster af samme klasse / implementering af den samme grænseflade. For at identificere dem bruger vi bindende kommentarer. Vi vil se nogle eksempler, når vi forklarer bindinger.

Sådan oprettes bindinger

Brugervejledningen i Guice forklarer det perfekt. Så jeg vil bare kopiere det her:

For at oprette bindinger skal du udvide AbstractModuleog tilsidesætte dens configuremetode. I metodekroppen skal du ringe for bind()at specificere hver binding. Disse metoder er typekontrolleret, så compileren kan rapportere fejl, hvis du bruger de forkerte typer. Når du har oprettet dine moduler, skal du sende dem som argumenter til Guice.createInjector()at bygge en injektor.

Der er en række typer bindinger: Linked, Instance, @Provides annotation, Provider bindings, Constructor bindings og Un-target bindings.

Til denne artikel vil jeg kun dække sammenkædede bindinger, instansbindinger, @Provides-kommentar og en særlig kommentar @Inject. Jeg bruger meget sjældent andre måder at binde på, men flere oplysninger kan findes på //github.com/google/guice/wiki/Bindings.

  1. Linked Binding: En Linked binding kortlægger en type / interface til dens implementering. Dette eksempel kortlægger grænsefladen MessageService til dens implementering EmailService.

Enkelt sagt: Når jeg beder Guice om at give mig en forekomst af MessageService, vil det give mig en forekomst af EmailService.

Men hvordan ved det at oprette en forekomst af EmailService ? Det får vi se senere.

public class MessagingModule extends AbstractModule { @Override protected void configure() { bind(MessageService.class).to(EmailService.class); }}

Måske vil vi have mere end en forekomst af MessageService i vores projekt. Nogle steder vil vi have en SMSService tilknyttet en MessageService snarere end en EmailService. I sådanne tilfælde bruger vi en bindende kommentar. For at oprette en bindende kommentar skal du oprette to kommentarer som sådan:

@BindingAnnotation @Target({ FIELD, PARAMETER, METHOD }) @Retention(RUNTIME)public @interface Email {}
@BindingAnnotation @Target({ FIELD, PARAMETER, METHOD }) @Retention(RUNTIME)public @interface SMS {}

Du behøver ikke at vide om metadataannotationerne (@Target, @ Retention). Hvis du er interesseret, kan du læse dette: //github.com/google/guice/wiki/BindingAnnotations

Når vi har kommentarerne med os, kan vi oprette to separate bindinger, som instruerer Guice om at oprette forskellige forekomster af MessageService baseret på BindingAnnotation (jeg tænker på det som en kvalifikator).

public class MessagingModule extends AbstractModule { @Override protected void configure() { bind(MessageService.class).annotatedWith(Email.class) .to(EmailService.class);
 bind(MessageService.class).annotatedWith(SMS.class) .to(SMSService.class); }}

2. Instansbinding: Binder en type til en bestemt forekomst

 bind(Integer.class) .annotatedWith(Names.named(“login timeout seconds”)) .toInstance(10);

Man bør undgå at bruge .toInstance med objekter, der er komplicerede at oprette, da det kan bremse opstart af applikationer. Du kan bruge en @Provides-metode i stedet. Faktisk kan du endda glemme, at vi nævnte noget om Instance binding lige nu.

3. @ Giver kommentar :

Dette er lige fra Guices wiki, da det er ret simpelt:

Brug en @Providesmetode, når du har brug for kode for at oprette et objekt . Metoden skal defineres inden for et modul, og den skal have en @Provideskommentar. Metodens returtype er den bundne type. Når injektoren har brug for en forekomst af den type, påberåber den sig metoden.
bind(MessageService.class)
.annotatedWith(Email.class)
.to(EmailService.class);

er det samme som

@[email protected] MessageService provideMessageService() { return new EmailService();}

hvor Email.java er en bindende kommentar.

Afhængigheder kan overføres til en metode med denne kommentar, som gør den yderst nyttig i projekter i det virkelige liv. For eksempel til den kode, der er nævnt nedenfor, udøver injektoren bindingen til strengparameteren apiKey, inden den påberåber metoden.

@Provides @PayPalCreditCardProcessor providePayPalCreditCardProcessor( @Named("PayPal API key") String apiKey) { PayPalCCProcessor processor = new PaypalCCProcessor(); processor.setApiKey(apiKey); return processor; }

4. @ Inject annotation (Just in Time binding): Whatever we covered up until now are called explicit bindings. If Guice, when trying to create an instance, does not find an explicit binding, it tries to create one using a Just-in-time binding.

Guice can create these bindings by using the class’s injectable constructor. This is either a non-private, no-arguments constructor or a constructor with the @Injectannotation.

Task

Now let’s move to the project we cloned from Github.

Like the examples in the previous article, this maven project implements a BillingService which charges a PizzaOrder using a credit card and generates a Receipt.

The project structure is as follows:

Interfaces

  • BillingService — charges an order using a credit card
  • CreditCardProcessor — debits some amount from a credit card
  • TransactionLog — logs results

Classes

src

  • CreditCard — entity representing a Credit Card
  • PizzaOrder — entity representing a Pizza order
  • Receipt — entity representing a receipt
  • RealBillingService implements BillingService
  • PaypalCreditCardProcessor implements CreditCardProcessor
  • BankCreditCardProcessor implements CreditCardProcessor
  • InMemoryTransactionLog implements TransactionLog
  • GuiceTest — Main class which uses BillingService
  • BillingModule — All Guice bindings go here
  • GuiceInjectionTest : Unit tests to check binding constraints

The task here is to create Guice Bindings in the BillingModule such that the following constraints are satisfied:

  1. All implementations of BillingService should be bound to RealBillingService.
  2. CreditCardProcessor interface annotated with @Paypal should be bound to PaypalCreditCardProcessor class.
  3. CreditCardProcessor interface named with string “Bank” should be bound to BankCreditCardProcessor class.
  4. BillingService instances returned by injector should have an instance of BankCreditCardProcessor as their dependency.
  5. All implementations of TransactionLog should be bound to InMemoryTransactionLog.

All five unit tests in GuiceInjectionTests should pass if the above conditions are satisfied. You should also be able to run the main method in GuiceTest.

To test correctness:

  1. run unit tests
mvn test

This should run the test file GuiceInjectionTests.java.

2. run the main file

mvn exec:java -Dexec.mainClass="GuiceTest"

This should execute the main class of the project, which does the end to end work of creating an order, processes payment using a credit card and generates a receipt.

Du kan kommentere, hvis du har spørgsmål, og jeg vil prøve at besvare dem. Bemærk, at der ikke er noget korrekt svar på denne øvelse. DM mig dine løsninger, og jeg vil tilføje svarene til lageret. Eller endnu bedre, send mig en anmodning om træk :)