Guice vs Spring - Dependency Injection

1. Inleiding

Google Guice en Voorjaar zijn twee robuuste kaders die worden gebruikt voor injectie van afhankelijkheden. Beide frameworks dekken alle noties van afhankelijkheidsinjectie, maar elk heeft zijn eigen manier om ze te implementeren.

In deze tutorial bespreken we hoe de Guice- en Spring-frameworks verschillen in configuratie en implementatie.

2. Maven afhankelijkheden

Laten we beginnen met het toevoegen van de Guice- en Spring Maven-afhankelijkheden aan onze pom.xml het dossier:

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

We hebben altijd toegang tot het laatste lente-context of guice afhankelijkheden van Maven Central.

3. Configuratie van afhankelijkheidsinjectie

Dependency injection is een programmeertechniek die we gebruiken om onze klassen onafhankelijk te maken van hun afhankelijkheden.

In deze sectie zullen we verwijzen naar verschillende kernfuncties die verschillen tussen Spring en Guice in hun manier om afhankelijkheidsinjectie te configureren.

3.1. Lente bedrading

Spring declareert de afhankelijkheidsinjectieconfiguraties in een speciale configuratieklasse. Deze klasse moet worden geannoteerd door de @Configuratie annotatie. De Spring-container gebruikt deze klasse als een bron van bean-definities.

Klassen die worden beheerd door Spring worden gebeldLente bonen.

Spring gebruikt de @Autowired annotatie om de afhankelijkheden automatisch te bedraden. @Autowired maakt deel uit van de ingebouwde kernannotaties van Spring. We kunnen gebruiken @Autowired op lidvariabelen, setter-methoden en constructors.

De lente ondersteunt ook @Injecteren. @Injecteren maakt deel uit van de Java CDI (Contexts and Dependency Injection) die een standaard definieert voor afhankelijkheidsinjectie.

Laten we zeggen dat we automatisch een afhankelijkheid willen koppelen aan een lidvariabele. We kunnen het gewoon annoteren met @Autowired:

@Component openbare klasse UserService {@Autowired private AccountService accountService; }
@Component openbare klasse AccountServiceImpl implementeert AccountService {}

Ten tweede, laten we een configuratieklasse maken om te gebruiken als de bron van bonen terwijl we onze toepassingscontext laden:

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

Merk op dat we ook hebben geannoteerd Gebruikersservice en AccountServiceImpl met @Component om ze als bonen te registreren. Het is de @BuienRadarNL annotatie die Spring vertelt waar hij moet zoeken voor geannoteerde componenten.

Ook al hebben we geannoteerd AccountServiceImpl,De lente kan het toewijzen aan de AccountService sinds het implementeert AccountService.

Vervolgens moeten we een toepassingscontext definiëren om toegang te krijgen tot de bonen. Laten we opmerken dat we in al onze Spring-unit-tests naar deze context zullen verwijzen:

ApplicationContext context = nieuwe AnnotationConfigApplicationContext (SpringMainConfig.class);

Nu tijdens runtime kunnen we het EENccountService instantie van onze Gebruikersservice Boon:

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

3.2. Guice-binding

Guice beheert zijn afhankelijkheden in een speciale klasse, een module genaamd. Een Guice-module moet het AbstractModule class en overschrijven zijn configureren () methode.

Guice gebruikt binding als equivalent van bedrading in het voorjaar. Simpel gezegd, bindingen stellen ons in staat om te definiëren hoe afhankelijkheden in een klasse worden geïnjecteerd. Guice-bindingen worden gedeclareerd in onze module's configureren () methode.

In plaats van @Autowired, Guice gebruikt de @Injecteren annotatie om de afhankelijkheden te injecteren.

Laten we een gelijkwaardig Guice-voorbeeld maken:

openbare klasse GuiceUserService {@Inject private AccountService accountService; }

Ten tweede maken we de moduleklasse die een bron is van onze bindende definities:

public class GuiceModule breidt AbstractModule uit {@Override protected void configure () {bind (AccountService.class) .to (AccountServiceImpl.class); }}

Normaal gesproken verwachten we dat Guice elk afhankelijkheidsobject instantieert vanuit hun standaardconstructors als er geen binding expliciet is gedefinieerd in de configureren () methode. Maar aangezien interfaces niet direct kunnen worden geïnstantieerd, moeten we bindingen definiëren om Guice te vertellen welke interface aan welke implementatie zal worden gekoppeld.

Vervolgens moeten we een Injector gebruik makend van GuiceModule om voorbeelden van onze lessen te krijgen. Houd er rekening mee dat al onze Guice-tests dit zullen gebruiken Injector:

Injector injector = Guice.createInjector (nieuwe GuiceModule ());

Ten slotte halen we tijdens runtime een GuiceUserService instantie met een niet-nul accountService afhankelijkheid:

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

3.3. Spring's @Bean-annotatie

Spring biedt ook een annotatie op het niveau van de methode @Boon om bonen te registreren als alternatief voor annotaties op klasniveau, zoals @Component. De geretourneerde waarde van a @Boon geannoteerde methode wordt geregistreerd als een boon in de container.

Laten we zeggen dat we een exemplaar hebben van BookServiceImpl die we beschikbaar willen stellen voor injectie. We kunnen gebruiken @Boon om ons exemplaar te registreren:

@Bean openbare BookService bookServiceGenerator () {retourneer nieuwe BookServiceImpl (); }

En nu kunnen we een Boekservice Boon:

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

3.4. Guice's @ Biedt annotatie

Als een equivalent van Spring's @Boon annotatie, Guice heeft een ingebouwde annotatie @Biedt om hetzelfde werk te doen. Leuk vinden @Boon, @Biedt wordt alleen toegepast op de methoden.

Laten we nu het vorige Spring bean-voorbeeld implementeren met Guice. Het enige wat we hoeven te doen is de volgende code aan onze moduleklasse toe te voegen:

@Biedt openbare BookService bookServiceGenerator () {retourneer nieuwe BookServiceImpl (); }

En nu kunnen we een exemplaar ophalen van Boekservice:

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

3.5. Classpath Component Scanning in Spring

De lente biedt een @BuienRadarNL annotatie detecteert en instantieert automatisch geannoteerde componenten door vooraf gedefinieerde pakketten te scannen.

De @BuienRadarNL annotatie vertelt Spring welke pakketten zullen worden gescand op geannoteerde componenten. Het wordt gebruikt met @Configuratie annotatie.

3.6. Classpath Component Scanning in Guice

In tegenstelling tot de lente, Guice heeft niet zo'n functie voor het scannen van componenten. Maar het is niet moeilijk om het te simuleren. Er zijn enkele plug-ins zoals Gouverneur die deze functie in Guice kunnen brengen.

3.7. Objectherkenning in het voorjaar

De lente herkent objecten aan hun naam. De lente houdt de objecten vast in een structuur die ongeveer lijkt op een Kaart. Dit betekent dat we niet twee objecten met dezelfde naam kunnen hebben.

Bonenbotsing door meerdere bonen met dezelfde naam is een veelvoorkomend probleem Spring-ontwikkelaars slaan toe. Laten we bijvoorbeeld eens kijken naar de volgende bonenverklaringen:

@Configuration @Import ({SpringBeansConfig.class}) @ComponentScan ("com.baeldung.di.spring") openbare klasse SpringMainConfig {@Bean openbare BookService bookServiceGenerator () {retourneer nieuwe BookServiceImpl (); }}
@Configuration openbare klasse SpringBeansConfig {@Bean openbare AudioBookService bookServiceGenerator () {retourneer nieuwe AudioBookServiceImpl (); }}

Zoals we ons herinneren, hadden we al een bondefinitie voor Boekservice in SpringMainConfig klasse.

Om hier een bonenbotsing te creëren, moeten we de bean-methoden met dezelfde naam declareren. Maar we mogen niet twee verschillende methoden met dezelfde naam in één klasse hebben. Om die reden hebben we het AudioBookService bean in een andere configuratieklasse.

Laten we deze bonen nu verwijzen in een unit-test:

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

De unit-test zal mislukken met:

org.springframework.beans.factory.NoSuchBeanDefinitionException: geen kwalificerende bean van het type 'AudioBookService' beschikbaar

Ten eerste registreerde Spring het AudioBookService boon met "BookServiceGenerator" naam in de bonenkaart. Vervolgens moest het het overschrijven door de boondefinitie voor Boekservice vanwege de "Geen dubbele namen toegestaan" aard van de Hash kaart data structuur.

Ten slotte, we kunnen dit probleem oplossen door de namen van de bean-methoden uniek te maken of de naam attribuut toe aan een unieke naam voor elk @Boon.

3.8. Objectherkenning in Guice

In tegenstelling tot de lente, Guice heeft in feite een Kaart structuur. Dit betekent dat we niet meerdere bindingen aan hetzelfde type kunnen hebben zonder aanvullende metadata te gebruiken.

Guice biedt bindende annotaties om het definiëren van meerdere bindingen voor hetzelfde type mogelijk te maken. Laten we eens kijken wat er gebeurt als we twee verschillende bindingen hebben voor hetzelfde type in Guice.

openbare klas Persoon {}

Laten we nu twee verschillende binding aangeven voor de Persoon klasse:

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

En hier is hoe we een exemplaar kunnen krijgen van Persoon klasse:

Persoon person = injector.getInstance (Person.class); assertNotNull (persoon);

Dit zal mislukken met:

com.google.inject.CreationException: er was al een binding met Persoon geconfigureerd op GuiceModule.configure ()

We kunnen dit probleem verhelpen door simpelweg een van de bindingen voor het Persoon klasse.

3.9. Optionele afhankelijkheden in het voorjaar

Optionele afhankelijkheden zijn afhankelijkheden die niet vereist zijn bij het automatisch bedraden of injecteren van bonen.

Voor een veld dat is geannoteerd met @Autowired, als een bean met het overeenkomende gegevenstype niet in de context wordt gevonden, zal Spring gooien NoSuchBeanDefinitionException.

Echter, soms willen we autowiring overslaan voor sommige afhankelijkheden en ze laten zoals nulzonder een uitzondering te maken:

Laten we nu eens kijken naar het volgende voorbeeld:

@Component openbare klasse BookServiceImpl implementeert BookService {@Autowired private AuthorService authorService; }
openbare klasse AuthorServiceImpl implementeert AuthorService {}

Zoals we kunnen zien aan de hand van de bovenstaande code, AuthorServiceImpl klasse is niet geannoteerd als een component. En we gaan ervan uit dat er geen bean-declaratiemethode voor is in onze configuratiebestanden.

Laten we nu de volgende test uitvoeren om te zien wat er gebeurt:

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

Het is niet verrassend dat het zal mislukken met:

org.springframework.beans.factory.NoSuchBeanDefinitionException: geen kwalificerende bean van het type 'AuthorService' beschikbaar

We kunnen maken authorService afhankelijkheid optioneel door Java 8's te gebruiken Optioneel typ om deze uitzondering te vermijden.

public class BookServiceImpl implementeert BookService {@Autowired private Optioneel authorService; }

Nu, onze authorService afhankelijkheid is meer als een container die al dan niet een boon van bevat AuteurService type. Ook al is er geen boon voor AuteurService in onze toepassingscontext, onze authorService veld zal nog steeds niet-nul lege container. Daarom heeft de lente geen reden om te gooien NoSuchBeanDefinitionException.

Als alternatief voor Optioneel, we kunnen gebruiken @Autowired‘S verplicht attribuut, dat is ingesteld op waar standaard, om een ​​afhankelijkheid optioneel te maken. We kunnen de verplicht toe te schrijven aan false om een ​​afhankelijkheid optioneel te maken voor automatische bedrading.

Daarom zal Spring het injecteren van de afhankelijkheid overslaan als een bean voor zijn gegevenstype niet beschikbaar is in de context. De afhankelijkheid blijft ingesteld op nul:

@Component openbare klasse BookServiceImpl implementeert BookService {@Autowired (verplicht = false) private AuthorService authorService; }

Soms kan het handig zijn om afhankelijkheden als optioneel te markeren, aangezien niet alle afhankelijkheden altijd vereist zijn.

Met dit in gedachten moeten we niet vergeten dat we extra voorzichtig moeten zijn en nul-controles tijdens de ontwikkeling om NullPointerException vanwege de nul afhankelijkheden.

3.10. Optionele afhankelijkheden in Guice

Net als Voorjaar, Guice kan ook Java 8's gebruiken Optioneel typ om een ​​afhankelijkheid optioneel te maken.

Laten we zeggen dat we een klas willen maken en met een Foo afhankelijkheid:

openbare klasse FooProcessor {@Inject private Foo foo; }

Laten we nu een binding definiëren voor de Foo klasse:

bind (Foo.class) .toProvider (new Provider () {public Foo get () {return null;}});

Laten we nu proberen een exemplaar te krijgen van FooProcessor in een unit-test:

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

Onze unit-test zal mislukken met:

com.google.inject.ProvisionException: null geretourneerd door binding op GuiceModule.configure (..) maar de eerste parameter van FooProcessor. [...] is niet @Nullable

Om deze uitzondering over te slaan, kunnen we de foo afhankelijkheid optioneel met een eenvoudige update:

openbare klasse FooProcessor {@Inject private Optioneel foo; }

@Injecteren heeft geen verplicht attribuut om de afhankelijkheid optioneel te markeren. Een alternatieve benadering voor maken een afhankelijkheid die optioneel is in Guice is om de @Nullable annotatie.

Guice tolereert injecteren nul waarden in het geval van gebruik @Nullable zoals uitgedrukt in het uitzonderingsbericht hierboven. Laten we de @Nullable annotatie:

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

4. Implementaties van afhankelijkheidsinjectietypes

In deze sectie zullen we de afhankelijkheidsinjectie-typen bekijken en de implementaties van Spring en Guice vergelijken aan de hand van verschillende voorbeelden.

4.1. Constructor-injectie in het voorjaar

Bij afhankelijkheidsinjectie op basis van constructeurs geven we de vereiste afhankelijkheden door aan een klasse op het moment van instantiatie.

Laten we zeggen dat we een Spring-component willen hebben en afhankelijkheden willen toevoegen via de constructor ervan. We kunnen die constructor annoteren met @Autowired:

@Component openbare klasse SpringPersonService {privé Personensional personensional; @Autowired openbare SpringPersonService (Personensional personensional) {this.personensional = personugeot; }}

Beginnend met Spring 4, de @Autowired afhankelijkheid is niet vereist voor dit type injectie als de klasse slechts één constructor heeft.

Laten we een SpringPersonService boon in een test:

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

4.2. Constructor Injection in Guice

We kunnen het vorige voorbeeld herschikken naar implementeren constructor injectie in Guice. Merk op dat Guice @Injecteren in plaats van @Autowired.

openbare klasse GuicePersonService {privé Person. @Inject openbare GuicePersonService (Personensional personensional) {this.personensional = personensional; }}

Hier is hoe we een exemplaar kunnen krijgen van GuicePersonService klasse uit de injector in een test:

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

4.3. Setter of Method Injection in het voorjaar

In op setter gebaseerde afhankelijkheidsinjectie roept de container setter-methoden van de klasse aan, nadat de constructor is aangeroepen om de component te instantiëren.

Laten we zeggen dat we willen dat Spring een afhankelijkheid automatisch bedraadt met behulp van een setter-methode. We kunnen die setter-methode annoteren met @Autowired:

@Component openbare klasse SpringPersonService {privé Personensional personensional; @Autowired public void setPersonbao (Personensional personensional) {this.personensional = personugeot; }}

Elke keer dat we een exemplaar nodig hebben van SpringPersonService klasse, zal Spring het personensional veld door de setPersonbao () methode.

We kunnen een SpringPersonService bean en toegang tot zijn personensional veld in een test zoals hieronder:

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

4.4. Setter of methode-injectie in Guice

We zullen ons voorbeeld gewoon een beetje veranderen om te bereiken setter injectie in Guice.

openbare klasse GuicePersonService {privé Person. @Inject public void setPersonbao (Personensional personensional) {this.personensional = personensional; }}

Elke keer krijgen we een exemplaar van GuicePersonService klasse van de injector, we hebben de personensional veld doorgegeven aan de setter-methode hierboven.

Hier is hoe we een instantie kunnen maken van GuicePersonService klasse en toegang tot zijn personensional veld-in een test:

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

4.5. Veldinjectie in het voorjaar

We hebben in al onze voorbeelden al gezien hoe we veldinjectie kunnen toepassen voor zowel Spring als Guice. Het is dus geen nieuw concept voor ons. Maar laten we het gewoon nog een keer noemen voor de volledigheid.

In het geval van veldgebaseerde afhankelijkheidsinjectie, injecteren we de afhankelijkheden door ze te markeren met @Autowired of @Injecteren.

4.6. Veldinjectie in Guice

Zoals we in het bovenstaande gedeelte al hebben vermeld, hebben we het veldinjectie voor gebruik door Guice @Injecteren.

5. Conclusie

In deze tutorial hebben we de verschillende kernverschillen tussen Guice- en Spring-frameworks onderzocht in hun manieren om afhankelijkheidsinjectie te implementeren. Zoals altijd zijn de codevoorbeelden van Guice en Spring voorbij op GitHub.