Guice vs Spring - Afhængighedsinjektion

1. Introduktion

Google Guice og Forår er to robuste rammer, der bruges til afhængighedsinjektion. Begge rammer dækker alle forestillinger om afhængighedsinjektion, men hver har sin egen måde at implementere dem på.

I denne vejledning diskuterer vi, hvordan Guice- og Spring-rammerne adskiller sig i konfiguration og implementering.

2. Maven-afhængigheder

Lad os starte med at tilføje Guice og Spring Maven afhængigheder i vores pom.xml fil:

 org.springframework spring-context 5.1.4.RELEASE com.google.inject guice 4.2.2 

Vi kan altid få adgang til det nyeste forårskontekst eller guice afhængigheder fra Maven Central.

3. Afhængighedsinjektionskonfiguration

Afhængighedsindsprøjtning er en programmeringsteknik, som vi bruger til at gøre vores klasser uafhængige af deres afhængighed.

I dette afsnit henviser vi til flere kerneegenskaber, der adskiller sig mellem Spring og Guice på deres måder til konfiguration af afhængighedsinjektion.

3.1. Forårsledning

Spring erklærer konfigurationerne for afhængighedsinjektion i en særlig konfigurationsklasse. Denne klasse skal kommenteres af @Konfiguration kommentar. Spring container bruger denne klasse som en kilde til bønnedefinitioner.

Klasser administreret af Spring kaldesForår bønner.

Foråret bruger @Autowired kommentar til at koble afhængigheder automatisk. @Autowired er en del af Spring's indbyggede kernebemærkninger. Vi kan bruge @Autowired på medlemsvariabler, settermetoder og konstruktører.

Foråret understøtter også @Indsprøjte. @Indsprøjte er en del af Java CDI (Contexts and Dependency Injection), der definerer en standard for afhængighedsinjektion.

Lad os sige, at vi automatisk vil overføre en afhængighed til en medlemsvariabel. Vi kan simpelthen kommentere det med @Autowired:

@Komponent offentlig klasse UserService {@Autowired privat AccountService accountService; }
@Komponent offentlig klasse AccountServiceImpl implementerer AccountService {}

For det andet, lad os oprette en konfigurationsklasse, der skal bruges som kilde til bønner, mens vi indlæser vores applikationskontekst:

@Configuration @ComponentScan ("com.baeldung.di.spring") offentlig klasse SpringMainConfig {}

Bemærk, at vi også har kommenteret UserService og AccountServiceImpl med @Komponent at registrere dem som bønner. Det er @ComponentScan en kommentar, der fortæller foråret, hvor man skal søge til kommenterede komponenter.

Selvom vi har kommenteret AccountServiceImpl,Foråret kan kortlægge det til Kontoservice da det implementeres Kontoservice.

Derefter skal vi definere en applikationskontekst for at få adgang til bønnerne. Lad os bare bemærke, at vi vil henvise til denne sammenhæng i alle vores Spring Unit-tests:

ApplicationContext context = ny AnnotationConfigApplicationContext (SpringMainConfig.class);

Nu ved runtime kan vi hente ENccountService eksempel fra vores UserService bønne:

UserService userService = context.getBean (UserService.class); assertNotNull (userService.getAccountService ());

3.2. Guice Binding

Guice styrer sine afhængigheder i en speciel klasse kaldet et modul. Et Guice-modul skal udvide Abstrakt modul klasse og tilsidesætte dens konfigurer () metode.

Guice bruger binding som svarende til ledninger i foråret. Kort fortalt, bindinger giver os mulighed for at definere, hvordan afhængigheder skal injiceres i en klasse. Guice bindinger er angivet i vores moduler konfigurer () metode.

I stedet for @Autowired, Guice bruger @Indsprøjte kommentar for at indsprøjte afhængighederne.

Lad os oprette et tilsvarende Guice-eksempel:

offentlig klasse GuiceUserService {@Inject private AccountService accountService; }

For det andet opretter vi modulklassen, som er en kilde til vores bindende definitioner:

offentlig klasse GuiceModule udvider AbstractModule {@ Override-beskyttet ugyldig konfiguration () {bind (AccountService.class) .to (AccountServiceImpl.class); }}

Normalt forventer vi, at Guice instantierer hvert afhængighedsobjekt fra deres standardkonstruktører, hvis der ikke er nogen binding defineret eksplicit i konfigurer () metode. Men da grænseflader ikke kan instantieres direkte, er vi nødt til at definere bindinger at fortælle Guice, hvilken grænseflade der vil blive parret med hvilken implementering.

Derefter skal vi definere en Injektor ved brug af GuiceModule for at få forekomster af vores klasser. Lad os bare bemærke, at alle vores Guice-test bruger dette Injektor:

Injektorinjektor = Guice.createInjector (ny GuiceModule ());

Endelig når vi kører, henter vi en GuiceUserService eksempel med en ikke-nul accountService afhængighed:

GuiceUserService guiceUserService = injektor.getInstance (GuiceUserService.class); assertNotNull (guiceUserService.getAccountService ());

3.3. Forårets @Bean-kommentar

Forår giver også en annotering på metodeniveau @Bønne at registrere bønner som et alternativ til klassens annoteringer som f.eks @Komponent. Returneringsværdien af ​​en @Bønne annoteret metode registreres som en bønne i containeren.

Lad os sige, at vi har en forekomst af BookServiceImpl som vi ønsker at stille til rådighed til injektion. Vi kunne bruge @Bønne at registrere vores instans:

@Bean public BookService bookServiceGenerator () {returner ny BookServiceImpl (); }

Og nu kan vi få en BookService bønne:

BookService bookService = context.getBean (BookService.class); assertNotNull (bookService);

3.4. Guice's @ giver kommentar

Som svarende til Spring's @Bønne kommentar, Guice har en indbygget kommentar @Provides at gøre det samme job. Synes godt om @Bønne, @Provides anvendes kun til metoderne.

Lad os nu implementere det forrige eksempel på Spring Bean med Guice. Alt, hvad vi skal gøre er at tilføje følgende kode i vores modulklasse:

@Provides public BookService bookServiceGenerator () {returner ny BookServiceImpl (); }

Og nu kan vi hente en forekomst af BookService:

BookService bookService = injector.getInstance (BookService.class); assertNotNull (bookService);

3.5. Classpath-komponentscanning om foråret

Foråret giver en @ComponentScan annotation registrerer og instanterer automatisk kommenterede komponenter ved at scanne foruddefinerede pakker.

Det @ComponentScan annotation fortæller Spring, hvilke pakker der skal scannes for kommenterede komponenter. Det bruges med @Konfiguration kommentar.

3.6. Classpath Component Scanning i Guice

I modsætning til foråret, Guice har ikke sådan en komponentscanningsfunktion. Men det er ikke svært at simulere det. Der er nogle plugins som Guvernør der kan bringe denne funktion i Guice.

3.7. Objektgenkendelse om foråret

Foråret genkender objekter ved deres navne. Foråret holder genstandene i en struktur, der er omtrent som en Kort. Det betyder, at vi ikke kan have to objekter med samme navn.

Bønnekollision på grund af at have flere bønner med samme navn er et almindeligt problem Forårets udviklere ramte. Lad os for eksempel overveje følgende bønnedeklarationer:

@Configuration @Import ({SpringBeansConfig.class}) @ComponentScan ("com.baeldung.di.spring") offentlig klasse SpringMainConfig {@Bean public BookService bookServiceGenerator () {returner ny BookServiceImpl (); }}
@Configuration public class SpringBeansConfig {@Bean public AudioBookService bookServiceGenerator () {returner ny AudioBookServiceImpl (); }}

Som vi husker, havde vi allerede en bønnedefinition til BookService i SpringMainConfig klasse.

For at skabe en bønnekollision her er vi nødt til at erklære bønnemetoderne med samme navn. Men vi har ikke lov til at have to forskellige metoder med samme navn i en klasse. Af den grund erklærede vi, at AudioBookService bønne i en anden konfigurationsklasse.

Lad os nu henvise disse bønner til i en enhedstest:

BookService bookService = context.getBean (BookService.class); assertNotNull (bookService); AudioBookService audioBookService = context.getBean (AudioBookService.class); assertNotNull (audioBookService);

Enhedstesten mislykkes med:

org.springframework.beans.factory.NoSuchBeanDefinitionException: Ingen kvalificerende bønne af typen 'AudioBookService' tilgængelig

Først registrerede Spring det AudioBookService bønne med “BookServiceGenerator” navn på dets bønnekort. Derefter måtte det tilsidesætte det ved definitionen af ​​bønner til BookService på grund af den “Ingen duplikerede navne tilladt” arten af HashMap datastruktur.

Endelig vi kan løse dette problem ved at gøre navnene på bønnemetoder unikke eller indstille navn attribut til et unikt navn for hver @Bønne.

3.8. Objektgenkendelse i Guice

I modsætning til foråret, Guice har grundlæggende en Kort struktur. Dette betyder, at vi ikke kan have flere bindinger til den samme type uden at bruge yderligere metadata.

Guice giver bindende annoteringer for at muliggøre definition af flere bindinger for den samme type. Lad os se, hvad der sker, hvis vi har to forskellige bindinger til den samme type i Guice.

offentlig klasseperson {}

Lad os nu erklære to forskellige bindende for Person klasse:

bind (Person.class) .toConstructor (Person.class.getConstructor ()); bind (Person.class) .toProvider (new Provider () {public Person get () {Person p = new Person (); return p;}});

Og her er hvordan vi kan få en forekomst af Person klasse:

Person person = injektor.getInstance (Person.klasse); assertNotNull (person);

Dette mislykkes med:

com.google.inject.CreationException: En binding til person var allerede konfigureret på GuiceModule.configure ()

Vi kan løse dette problem ved blot at kassere en af ​​bindingerne til Person klasse.

3.9. Valgfri afhængighed om foråret

Valgfri afhængighed er afhængighed, som ikke er påkrævet ved automatisk ledningsføring eller injektion af bønner.

For et felt, der er kommenteret med @Autowired, hvis en bønne med matchende datatype ikke findes i sammenhængen, kaster Spring NoSuchBeanDefinitionException.

Imidlertid, nogle gange vil vi måske springe over autoledning for nogle afhængigheder og lade dem være nuluden at kaste en undtagelse:

Lad os nu se på følgende eksempel:

@Component public class BookServiceImpl implementerer BookService {@Autowired private AuthorService authorService; }
offentlig klasse AuthorServiceImpl implementerer AuthorService {}

Som vi kan se fra ovenstående kode, AuthorServiceImpl klasse er ikke blevet kommenteret som en komponent. Og vi antager, at der ikke er en bønnedeklarationsmetode til det i vores konfigurationsfiler.

Lad os nu køre følgende test for at se, hvad der sker:

BookService bookService = context.getBean (BookService.class); assertNotNull (bookService);

Ikke overraskende vil det mislykkes med:

org.springframework.beans.factory.NoSuchBeanDefinitionException: Ingen kvalificerende bønner af typen 'AuthorService' tilgængelig

Vi kan lave authorService afhængighed valgfri ved hjælp af Java 8'er Valgfri skriv for at undgå denne undtagelse.

offentlig klasse BookServiceImpl implementerer BookService {@Autowired private Valgfri authorService; }

Nu, vores authorService afhængighed er mere som en beholder, der måske eller måske ikke indeholder en bønne af AuthorService type. Selvom der ikke er en bønne til AuthorService i vores applikationskontekst, vores authorService felt vil stadig være ikke-nul tom beholder. Derfor har Spring ikke nogen grund til at kaste NoSuchBeanDefinitionException.

Som et alternativ til Valgfri, vi kan bruge @Autowired'S krævet attribut, som er indstillet til rigtigt som standard for at gøre en afhængighed valgfri. Vi kan indstille krævet attribut til falsk at gøre en afhængighed valgfri til autoledning.

Derfor springer Spring over at injicere afhængigheden, hvis en bønne for dens datatype ikke er tilgængelig i sammenhængen. Afhængigheden forbliver indstillet til nul:

@Komponent offentlig klasse BookServiceImpl implementerer BookService {@Autowired (krævet = falsk) privat AuthorService authorService; }

Nogle gange kan markering af afhængigheder valgfri være nyttig, da ikke alle afhængigheder altid kræves.

Med dette i tankerne skal vi huske, at vi bliver nødt til at være ekstra forsigtige og nul-kontrol under udvikling for at undgå enhver NullPointerException på grund af den nul afhængigheder.

3.10. Valgfri afhængighed i Guice

Ligesom Forår, Guice kan også bruge Java 8'er Valgfri skriv for at gøre en afhængighed valgfri.

Lad os sige, at vi vil oprette en klasse og med en Foo afhængighed:

offentlig klasse FooProcessor {@Inject private Foo foo; }

Lad os nu definere en binding til Foo klasse:

bind (Foo.class) .toProvider (ny udbyder () {public Foo get () {return null;}});

Lad os nu prøve at få en forekomst af FooProcessor i en enhedstest:

FooProcessor fooProcessor = injektor.getInstance (FooProcessor.class); assertNotNull (fooProcessor);

Vores enhedstest mislykkes med:

com.google.inject.ProvisionException: null returneret ved binding på GuiceModule.configure (..) men FooProcessorens 1. parameter. [...] er ikke @Nullable

For at springe denne undtagelse over kan vi lave foo afhængighed valgfri med en simpel opdatering:

offentlig klasse FooProcessor {@Inject private Valgfri foo; }

@Indsprøjte har ikke en krævet attribut for at markere afhængigheden valgfri. En alternativ tilgang til lave en afhængighed valgfri i Guice er at bruge @Nullable kommentar.

Guice tolererer injektion nul værdier i tilfælde af brug @Nullable som udtrykt i ovenstående undtagelsesmeddelelse. Lad os anvende @Nullable kommentar:

offentlig klasse FooProcessor {@Inject @Nullable private Foo foo; }

4. Implementeringer af afhængighedsinjektionstyper

I dette afsnit tager vi et kig på afhængighedsinjektionstyperne og sammenligner implementeringerne fra Spring og Guice ved at gennemgå flere eksempler.

4.1. Konstruktørinjektion om foråret

I konstruktørbaseret afhængighedsinjektion videregiver vi de krævede afhængigheder i en klasse på tidspunktet for instantiering.

Lad os sige, at vi vil have en Spring-komponent, og vi vil tilføje afhængigheder gennem dens konstruktør. Vi kan kommentere konstruktøren med @Autowired:

@Komponent offentlig klasse SpringPersonService {privat PersonDao personDao; @Autowired public SpringPersonService (PersonDao personDao) {this.personDao = personDao; }}

Startende med forår 4, den @Autowired afhængighed er ikke påkrævet for denne type injektion, hvis klassen kun har en konstruktør.

Lad os hente en SpringPersonService bønne i en test:

SpringPersonService personService = context.getBean (SpringPersonService.class); assertNotNull (personService);

4.2. Konstruktørinjektion i Guice

Vi kan omarrangere det forrige eksempel til implementere konstruktionsinjektion i Guice. Bemærk, at Guice bruger @Indsprøjte i stedet for @Autowired.

offentlig klasse GuicePersonService {privat PersonDao personDao; @Inject public GuicePersonService (PersonDao personDao) {this.personDao = personDao; }}

Her er hvordan vi kan få en forekomst af GuicePersonService klasse fra injektor i en test:

GuicePersonService personService = injector.getInstance (GuicePersonService.class); assertNotNull (personService);

4.3. Setter eller metodeinjektion om foråret

Ved setterbaseret afhængighedsinjektion kalder beholderen settermetoder i klassen efter at have påkaldt konstruktøren for at instantiere komponenten.

Lad os sige, at vi ønsker, at Spring autowire en afhængighed ved hjælp af en setter-metode. Vi kan kommentere denne setter-metode med @Autowired:

@Komponent offentlig klasse SpringPersonService {privat PersonDao personDao; @Autowired public void setPersonDao (PersonDao personDao) {this.personDao = personDao; }}

Når vi har brug for en forekomst af SpringPersonService klasse, forår vil autowire personDao felt ved at påberåbe sig setPersonDao () metode.

Vi kan få en SpringPersonService bønne og få adgang til dens personDao felt i en test som nedenfor:

SpringPersonService personService = context.getBean (SpringPersonService.class); assertNotNull (personService); assertNotNull (personService.getPersonDao ());

4.4. Setter eller metodeinjektion i Guice

Vi ændrer blot vores eksempel lidt for at opnå setterinjektion i Guice.

offentlig klasse GuicePersonService {privat PersonDao personDao; @Inject public void setPersonDao (PersonDao personDao) {this.personDao = personDao; }}

Hver gang vi får en forekomst af GuicePersonService klasse fra injektoren, får vi personDao felt overført til setter-metoden ovenfor.

Her er hvordan vi kan oprette en forekomst af GuicePersonService klasse og få adgang til dens personDao Marki en test:

GuicePersonService personService = injector.getInstance (GuicePersonService.class); assertNotNull (personService); assertNotNull (personService.getPersonDao ());

4.5. Feltinjektion om foråret

Vi har allerede set, hvordan vi anvender feltinjektion både til Spring og Guice i alle vores eksempler. Så det er ikke et nyt koncept for os. Men lad os bare liste det igen for fuldstændighed.

I tilfælde af feltbaseret afhængighedsinjektion injicerer vi afhængighederne ved at markere dem med @Autowired eller @Indsprøjte.

4.6. Feltinjektion i Guice

Som vi nævnte i afsnittet ovenfor, har vi allerede dækket feltinjektion til Guice ved hjælp af @Indsprøjte.

5. Konklusion

I denne vejledning undersøgte vi de forskellige kerneforskelle mellem Guice og Spring-rammer på deres måder at implementere afhængighedsinjektion på. Som altid er Guice- og Spring-kodeeksempler overstået på GitHub.