Een inleiding tot CDI (Contexts and Dependency Injection) in Java

1. Overzicht

CDI (Contexts and Dependency Injection) is een standaard framework voor afhankelijkheidsinjectie dat is opgenomen in Java EE 6 en hoger.

Het stelt ons in staat om de levenscyclus van stateful componenten te beheren via domeinspecifieke levenscycluscontexten en componenten (services) in clientobjecten te injecteren op een typeveilige manier.

In deze zelfstudie gaan we dieper in op de meest relevante functies van CDI en implementeren we verschillende benaderingen voor het injecteren van afhankelijkheden in clientklassen.

2. DYDI (doe-het-zelf-afhankelijkheidsinjectie)

In een notendop: het is mogelijk om DI te implementeren zonder toevlucht te nemen tot een raamwerk.

Deze benadering staat in de volksmond bekend als DYDI (Do-it-Yourself Dependency Injection).

Met DYDI houden we applicatiecode geïsoleerd van het maken van objecten door de vereiste afhankelijkheden door te geven aan de clientklassen via gewone oude fabrieken / bouwers.

Hier ziet u hoe een eenvoudige DYDI-implementatie eruit zou kunnen zien:

openbare interface TextService {String doSomethingWithText (String-tekst); String doSomethingElseWithText (String-tekst); }
openbare klasse SpecializedTextService implementeert TextService {...}
openbare klasse TextClass {privé TextService textService; // constructor}
openbare klasse TextClassFactory {openbare TextClass getTextClass () {retourneer nieuwe TextClass (nieuwe SpecializedTextService ();}}

Uiteraard is DYDI geschikt voor enkele relatief eenvoudige gebruikssituaties.

Als onze voorbeeldtoepassing in omvang en complexiteit zou toenemen en een groter netwerk van onderling verbonden objecten zou implementeren, zouden we het uiteindelijk vervuilen met tonnen objectgrafiekfabrieken.

Dit zou veel standaardcode vereisen, alleen voor het maken van objectgrafieken. Dit is geen volledig schaalbare oplossing.

Kunnen we DI nog beter doen? Natuurlijk kunnen we. Dit is precies waar CDI in beeld komt.

3. Een eenvoudig voorbeeld

CDI verandert DI in een no-brainer-proces, wat neerkomt op het versieren van de serviceklassen met een paar eenvoudige annotaties en het definiëren van de overeenkomstige injectiepunten in de klantklassen.

Om te laten zien hoe CDI DI op het meest basale niveau implementeert, gaan we ervan uit dat we een eenvoudige applicatie voor het bewerken van beeldbestanden willen ontwikkelen. Geschikt voor het openen, bewerken, schrijven, opslaan van een afbeeldingsbestand enzovoort.

3.1. De "Beans.xml" het dossier

Eerst moeten we een "Beans.xml" bestand in het "Src / main / resources / META-INF /" map. Zelfs als dit bestand helemaal geen specifieke DI-richtlijnen bevat, is het vereist om CDI in gebruik te nemen:

3.2. De serviceklassen

Laten we vervolgens de serviceklassen maken die de bovengenoemde bewerkingen uitvoeren op GIF-, JPG- en PNG-bestanden:

openbare interface ImageFileEditor {String openFile (String bestandsnaam); String editFile (String bestandsnaam); String writeFile (String bestandsnaam); String saveFile (String bestandsnaam); }
publieke klasse GifFileEditor implementeert ImageFileEditor {@Override public String openFile (String bestandsnaam) {return "Opening GIF-bestand" + bestandsnaam; } @Override public String editFile (String bestandsnaam) {return "GIF-bestand bewerken" + bestandsnaam; } @Override openbare String writeFile (String bestandsnaam) {return "Schrijven van GIF-bestand" + bestandsnaam; } @Override public String saveFile (String bestandsnaam) {return "GIF-bestand opslaan" + bestandsnaam; }}
public class JpgFileEditor implementeert ImageFileEditor {// JPG-specifieke implementaties voor openFile () / editFile () / writeFile () / saveFile () ...}
public class PngFileEditor implementeert ImageFileEditor {// PNG-specifieke implementaties voor openFile () / editFile () / writeFile () / saveFile () ...}

3.3. De cliëntklasse

Laten we tot slot een client class implementeren die een ImageFileEditor implementatie in de constructor, en laten we een injectiepunt definiëren met de @Injecteren annotatie:

openbare klasse ImageFileProcessor {privé ImageFileEditor imageFileEditor; @Inject openbare ImageFileProcessor (ImageFileEditor imageFileEditor) {this.imageFileEditor = imageFileEditor; }}

Simpel gezegd, de @Injecteren annotatie is het eigenlijke werkpaard van CDI. Het stelt ons in staat om injectiepunten te definiëren in de klantklassen.

In dit geval, @Injecteren instrueert CDI om een ImageFileEditor implementatie in de constructor.

Bovendien is het ook mogelijk om een ​​service te injecteren met behulp van de @Injecteren annotatie in velden (veldinjectie) en setters (setterinjectie). We zullen deze opties later bekijken.

3.4. Het bouwen van het ImageFileProcessor Objectgrafiek Met Las

Natuurlijk moeten we ervoor zorgen dat CDI het juiste injecteert ImageFileEditor implementatie in de ImageFileProcessor klasse constructor.

Om dit te doen, moeten we eerst een instantie van de klasse krijgen.

Omdat we niet afhankelijk zijn van een Java EE-applicatieserver voor het gebruik van CDI, doen we dit met Weld, de CDI-referentie-implementatie in Java SE:

public static void main (String [] args) {Weld weld = new Weld (); WeldContainer-container = weld.initialize (); ImageFileProcessor imageFileProcessor = container.select (ImageFileProcessor.class) .get (); System.out.println (imageFileProcessor.openFile ("file1.png")); container.shutdown (); } 

Hier maken we een WeldContainer object, en dan een ImageFileProcessor object, en tenslotte zijn open bestand() methode.

Zoals verwacht, zal CDI luid klagen als we de applicatie uitvoeren door een DeploymentException:

Ontevreden afhankelijkheden voor type ImageFileEditor met kwalificaties @Default op injectiepunt ...

We krijgen deze uitzondering omdat CDI niet weet wat ImageFileEditor implementatie om te injecteren in het ImageFileProcessor constructeur.

In de terminologie van CDIstaat dit bekend als een dubbelzinnige injectie-uitzondering.

3.5. De @Standaard en @Alternatief Annotaties

Het oplossen van deze dubbelzinnigheid is eenvoudig. CDI annoteert standaard alle implementaties van een interface met de @Standaard annotatie.

We moeten dus expliciet vertellen welke implementatie in de clientklasse moet worden geïnjecteerd:

@Alternative openbare klasse GifFileEditor implementeert ImageFileEditor {...}
@Alternative openbare klasse JpgFileEditor implementeert ImageFileEditor {...} 
openbare klasse PngFileEditor implementeert ImageFileEditor {...}

In dit geval hebben we geannoteerd GifFileEditor en JpgFileEditor met de @Alternatief annotatie, dus CDI weet dat nu PngFileEditor (standaard geannoteerd met de @Standaard annotatie) is de implementatie die we willen injecteren.

Als we de applicatie opnieuw uitvoeren, wordt deze deze keer uitgevoerd zoals verwacht:

PNG-bestand file1.png openen 

Verder annoteren PngFileEditor met de @Standaard annotatie en het behouden van de andere implementaties als alternatieven zal hetzelfde bovenstaande resultaat opleveren.

Dit toont, in een notendop, hoe we heel gemakkelijk de run-time injectie van implementaties kunnen omwisselen door simpelweg de @Alternatief annotaties in de serviceklassen.

4. Veldinjectie

CDI ondersteunt zowel veld- als setterinjectie uit de doos.

Hier leest u hoe u veldinjectie uitvoert (de regels voor kwalificerende services met de @Standaard en @Alternatief annotaties blijven hetzelfde):

@Inject privé definitief ImageFileEditor imageFileEditor;

5. Setterinjectie

Evenzo, hier is hoe u setter-injectie kunt doen:

@Inject public void setImageFileEditor (ImageFileEditor imageFileEditor) {...}

6. Het @Genaamd Annotatie

Tot nu toe hebben we geleerd hoe we injectiepunten in klantklassen kunnen definiëren en services kunnen injecteren met de @Injecteren, @Standaard , en @Alternatief annotaties, die de meeste gebruiksscenario's dekken.

Desalniettemin stelt CDI ons ook in staat om service-injectie uit te voeren met de @Genaamd annotatie.

Deze methode biedt een meer semantische manier om services te injecteren, door een betekenisvolle naam aan een implementatie te binden:

@Named ("GifFileEditor") openbare klasse GifFileEditor implementeert ImageFileEditor {...} @Named ("JpgFileEditor") openbare klasse JpgFileEditor implementeert ImageFileEditor {...} @Named ("PngFileEditor") openbare klasse PngFileEditor implementeert ImageFile ... }

Nu moeten we het injectiepunt in de ImageFileProcessor klasse die overeenkomt met een benoemde implementatie:

@Inject openbare ImageFileProcessor (@Named ("PngFileEditor") ImageFileEditor imageFileEditor) {...}

Het is ook mogelijk om veld- en setter-injectie uit te voeren met benoemde implementaties, wat erg lijkt op het gebruik van de @Standaard en @Alternatief annotaties:

@Inject private finale @Named ("PngFileEditor") ImageFileEditor imageFileEditor; @Inject public void setImageFileEditor (@Named ("PngFileEditor") ImageFileEditor imageFileEditor) {...}

7. Het @Produceert Annotatie

Soms vereist een service een bepaalde configuratie om volledig te worden geïnitialiseerd voordat deze wordt geïnjecteerd om extra afhankelijkheden af ​​te handelen.

CDI biedt ondersteuning voor deze situaties via het @Produceert annotatie.

@Produceert stelt ons in staat fabrieksklassen te implementeren, waarvan de verantwoordelijkheid het creëren van volledig geïnitialiseerde services is.

Om te begrijpen hoe de @Produceert annotatie werkt, laten we het ImageFileProcessor klasse, dus het kan een extra kosten TimeLogger service in de constructor.

De service wordt gebruikt voor het loggen van het tijdstip waarop een bepaalde bewerking van een afbeeldingsbestand wordt uitgevoerd:

@Inject public ImageFileProcessor (ImageFileEditor imageFileEditor, TimeLogger timeLogger) {...} public String openFile (String bestandsnaam) {return imageFileEditor.openFile (bestandsnaam) + "at:" + timeLogger.getTime (); } // aanvullende methoden voor afbeeldingsbestanden 

In dit geval is het TimeLogger les duurt twee extra services, SimpleDateFormat en Kalender:

openbare klasse TimeLogger {privé SimpleDateFormat dateFormat; privé kalender kalender; // constructors public String getTime () {return dateFormat.format (calendar.getTime ()); }}

Hoe vertellen we CDI waar we naar moeten kijken om een ​​volledig geïnitialiseerd TimeLogger voorwerp?

We maken gewoon een TimeLogger factory class en annoteer de fabrieksmethode met de @Produceert annotatie:

openbare klasse TimeLoggerFactory {@Produces openbare TimeLogger getTimeLogger () {retourneer nieuwe TimeLogger (nieuwe SimpleDateFormat ("HH: mm"), Calendar.getInstance ()); }}

Elke keer dat we een ImageFileProcessor CDI scant bijvoorbeeld het TimeLoggerFactory class en bel dan de getTimeLogger () methode (zoals het is geannoteerd met de @Produceert annotatie) en injecteer tenslotte de Tijdregistratie onderhoud.

Als we de gerefactureerde voorbeeldtoepassing uitvoeren met Lassen, het zal het volgende uitvoeren:

PNG-bestand file1.png openen om: 17:46

8. Aangepaste kwalificaties

CDI ondersteunt het gebruik van aangepaste kwalificaties voor het kwalificeren van afhankelijkheden en het oplossen van dubbelzinnige injectiepunten.

Aangepaste kwalificaties zijn een zeer krachtige functie. Ze binden niet alleen een semantische naam aan een service, maar ze binden ook injectie-metadata. Metadata zoals de RetentionPolicy en de juridische annotatiedoelen (ElementType).

Laten we eens kijken hoe we aangepaste kwalificaties in onze applicatie kunnen gebruiken:

@Qualifier @Retention (RetentionPolicy.RUNTIME) @Target ({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE, ElementType.PARAMETER}) openbaar @interface GifFileEditorQualifier {} 
@Qualifier @Retention (RetentionPolicy.RUNTIME) @Target ({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE, ElementType.PARAMETER}) openbaar @interface JpgFileEditorQualifier {} 
@Qualifier @Retention (RetentionPolicy.RUNTIME) @Target ({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE, ElementType.PARAMETER}) openbaar @interface PngFileEditorQualifier {} 

Laten we nu de aangepaste kwalificaties binden aan de ImageFileEditor implementaties:

@GifFileEditorQualifier openbare klasse GifFileEditor implementeert ImageFileEditor {...} 
@JpgFileEditorQualifier openbare klasse JpgFileEditor implementeert ImageFileEditor {...}
@PngFileEditorQualifier openbare klasse PngFileEditor implementeert ImageFileEditor {...} 

Laten we tot slot het injectiepunt in de ImageFileProcessor klasse:

@Inject openbare ImageFileProcessor (@PngFileEditorQualifier ImageFileEditor imageFileEditor, TimeLogger timeLogger) {...} 

Als we onze applicatie opnieuw starten, zou deze dezelfde uitvoer moeten genereren als hierboven weergegeven.

Aangepaste kwalificaties bieden een nette semantische benadering voor het binden van namen en annotatiemetagegevens aan implementaties.

Daarnaast, aangepaste kwalificaties stellen ons in staat om meer beperkende typeveilige injectiepunten te definiëren (beter dan de functionaliteit van de @Default- en @Alternative-annotaties).

Als alleen een subtype is gekwalificeerd in een typehiërarchie, dan zal CDI alleen het subtype injecteren, niet het basistype.

9. Conclusie

Ongetwijfeld CDI maakt afhankelijkheidsinjectie een no-brainer, de kosten van de extra annotaties zijn zeer weinig moeite voor het winnen van georganiseerde afhankelijkheidsinjectie.

Er zijn tijden dat DYDI nog steeds een plaats heeft boven CDI. Zoals bij het ontwikkelen van vrij eenvoudige applicaties die alleen eenvoudige objectgrafieken bevatten.

Zoals altijd zijn alle codevoorbeelden die in dit artikel worden getoond, beschikbaar op GitHub.