En introduktion til CDI (Contexts and Dependency Injection) i Java

1. Oversigt

CDI (Contexts and Dependency Injection) er en standardramme for afhængighedsinjektion inkluderet i Java EE 6 og højere.

Det giver os mulighed for at styre livscyklussen for statefulde komponenter via domænespecifikke livscykluskontekster og indsprøjte komponenter (tjenester) i klientobjekter på en typesikker måde.

I denne vejledning tager vi et dybtgående kig på CDIs mest relevante funktioner og implementerer forskellige tilgange til indsprøjtning af afhængigheder i klientklasser.

2. DYDI (gør-det-selv-afhængighedsinjektion)

Kort sagt er det muligt at implementere DI uden at bruge nogen ramme overhovedet.

Denne tilgang er populært kendt som DYDI (gør-det-selv-afhængighedsinjektion).

Med DYDI holder vi applikationskoden isoleret fra oprettelse af objekter ved at sende de krævede afhængigheder ind i klientklasserne gennem almindelige gamle fabrikker / bygherrer.

Sådan ser en grundlæggende DYDI-implementering ud:

offentlig grænseflade TextService {String doSomethingWithText (String text); String doSomethingElseWithText (strengtekst); }
offentlig klasse SpecializedTextService implementerer TextService {...}
offentlig klasse TextClass {privat TextService textService; // konstruktør}
offentlig klasse TextClassFactory {offentlig TextClass getTextClass () {returner ny TextClass (ny SpecializedTextService ();}}

Naturligvis er DYDI velegnet til nogle relativt enkle brugssager.

Hvis vores prøveapplikation voksede i størrelse og kompleksitet og implementerede et større netværk af sammenkoblede objekter, ville vi ende med at forurene den med masser af objektgraffabrikker.

Dette ville kræve en masse kedelpladekode bare for at oprette objektgrafer. Dette er ikke en fuldt skalerbar løsning.

Kan vi gøre DI bedre? Selvfølgelig kan vi det. Her er præcis, hvor CDI kommer ind i billedet.

3. Et simpelt eksempel

CDI forvandler DI til en no-brainer-proces, kogt ned til blot at dekorere serviceklasser med et par enkle kommentarer, og definere de tilsvarende indsprøjtningspunkter i klientklasserne.

For at vise hvordan CDI implementerer DI på det mest basale niveau, lad os antage, at vi ønsker at udvikle en simpel billedfilredigeringsapplikation. Kan åbne, redigere, skrive, gemme en billedfil og så videre.

3.1. Det “Bønner.xml” Fil

Først skal vi placere en “Bønner.xml” fil i “Src / main / ressourcer / META-INF /” folder. Selvom denne fil overhovedet ikke indeholder nogen specifikke DI-direktiver, er den påkrævet for at få CDI i gang:

3.2. Serviceklasser

Lad os derefter oprette serviceklasser, der udfører ovennævnte filhandlinger på GIF-, JPG- og PNG-filer:

offentlig grænseflade ImageFileEditor {String openFile (String fileName); String editFile (String fileName); String writeFile (String fileName); String saveFile (String filnavn); }
offentlig klasse GifFileEditor implementerer ImageFileEditor {@ Override public String openFile (String fileName) {return "Åbning af GIF-fil" + filnavn; } @ Override public String editFile (String fileName) {return "Editing GIF file" + fileName; } @ Override public String writeFile (String fileName) {return "Writing GIF file" + fileName; } @ Override public String saveFile (String fileName) {return "Saving GIF file" + fileName; }}
offentlig klasse JpgFileEditor implementerer ImageFileEditor {// JPG-specifikke implementeringer til openFile () / editFile () / writeFile () / saveFile () ...}
offentlig klasse PngFileEditor implementerer ImageFileEditor {// PNG-specifikke implementeringer til openFile () / editFile () / writeFile () / saveFile () ...}

3.3. Klientklassen

Lad os endelig implementere en klientklasse, der tager en ImageFileEditor implementering i konstruktøren, og lad os definere et indsprøjtningspunkt med @Indsprøjte kommentar:

offentlig klasse ImageFileProcessor {privat ImageFileEditor imageFileEditor; @Inject public ImageFileProcessor (ImageFileEditor imageFileEditor) {this.imageFileEditor = imageFileEditor; }}

Kort sagt, den @Indsprøjte kommentar er CDIs faktiske arbejdshest. Det giver os mulighed for at definere indsprøjtningspunkter i klientklasserne.

I dette tilfælde, @Indsprøjte pålægger CDI at injicere en ImageFileEditor implementering i konstruktøren.

Desuden er det også muligt at indsprøjte en tjeneste ved hjælp af @Indsprøjte kommentar i felter (feltinjektion) og settere (setterinjektion). Vi ser på disse muligheder senere.

3.4. Opbygning af ImageFileProcessor Objektgraf med svejsning

Vi skal selvfølgelig sørge for, at CDI injicerer det rigtige ImageFileEditor implementering i ImageFileProcessor klassekonstruktør.

For at gøre det skal vi først få en instans af klassen.

Da vi ikke stoler på nogen Java EE-applikationsserver til brug af CDI, gør vi dette med Weld, implementeringen af ​​CDI-reference i 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 (); } 

Her opretter vi en Svejsecontainer objekt og derefter få en ImageFileProcessor objekt og til sidst kalder det åben fil() metode.

Som forventet, hvis vi kører applikationen, vil CDI klage højt ved at smide en Implementeringsundtagelse:

Utilfredse afhængigheder for typen ImageFileEditor med kvalifikatorer @Default ved injektionsstedet ...

Vi får denne undtagelse, fordi CDI ikke ved hvad ImageFileEditor implementering at injicere i ImageFileProcessor konstruktør.

I CDI's terminologi, dette er kendt som en tvetydig injektionsundtagelse.

3.5. Det @Standard og @Alternativ Kommentarer

Det er let at løse denne tvetydighed. CDI annoterer som standard alle implementeringer af en grænseflade med @Standard kommentar.

Så vi skal eksplicit fortælle det, hvilken implementering der skal injiceres i klientklassen:

@Alternative public class GifFileEditor implementerer ImageFileEditor {...}
@Alternative public class JpgFileEditor implementerer ImageFileEditor {...} 
offentlig klasse PngFileEditor implementerer ImageFileEditor {...}

I dette tilfælde har vi kommenteret GifFileEditor og JpgFileEditor med @Alternativ kommentar, så CDI ved det nu PngFileEditor (annoteret som standard med @Standard annotation) er den implementering, vi vil indsprøjte.

Hvis vi kører applikationen igen, udføres den denne gang som forventet:

Åbning af PNG-fil file1.png 

Desuden kommentere PngFileEditor med @Standard kommentar og at beholde de andre implementeringer som alternativer vil give det samme ovenstående resultat.

Dette viser, i en nøddeskal, hvordan vi meget let kan bytte runtime-injektion af implementeringer ved blot at skifte @Alternativ kommentarer i serviceklasser.

4. Feltinjektion

CDI understøtter både felt- og setterinjektion ud af kassen.

Sådan udføres feltinjektion (reglerne for kvalificerede tjenester med @Standard og @Alternativ annoteringer forbliver de samme):

@Inject private final ImageFileEditor imageFileEditor;

5. Setterinjektion

Tilsvarende er her, hvordan man foretager setterinjektion:

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

6. Den @Som hedder Kommentar

Indtil videre har vi lært, hvordan man definerer indsprøjtningspunkter i klientklasser og indsprøjter tjenester med @Indsprøjte, @Standard og @Alternativ annoteringer, der dækker de fleste brugssager.

Ikke desto mindre tillader CDI os også at udføre serviceinjektion med @Som hedder kommentar.

Denne metode giver en mere semantisk måde at indsprøjte tjenester ved at binde et meningsfuldt navn til en implementering:

@Named ("GifFileEditor") offentlig klasse GifFileEditor implementerer ImageFileEditor {...} @Named ("JpgFileEditor") offentlig klasse JpgFileEditor implementerer ImageFileEditor {...} @Named ("PngFileEditor") offentlig klasse PngFileEditor implementerer ImageFileEditor }

Nu skal vi omlægge injektionsstedet i ImageFileProcessor klasse for at matche en navngivet implementering:

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

Det er også muligt at udføre felt- og setterinjektion med navngivne implementeringer, som ligner meget at bruge @Standard og @Alternativ kommentarer:

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

7. Den @Produces Kommentar

Nogle gange kræver en tjeneste, at en eller anden konfiguration initialiseres fuldt ud, før den injiceres for at håndtere yderligere afhængigheder.

CDI yder support til disse situationer gennem @Produces kommentar.

@Produces giver os mulighed for at implementere fabriksklasser, hvis ansvar er oprettelsen af ​​fuldt initialiserede tjenester.

At forstå, hvordan @Produces annotering fungerer, lad os omformulere ImageFileProcessor klasse, så det kan tage en ekstra TimeLogger service i konstruktøren.

Tjenesten bruges til at logge det tidspunkt, hvor en bestemt billedfilhandling udføres:

@Inject public ImageFileProcessor (ImageFileEditor imageFileEditor, TimeLogger timeLogger) {...} public String openFile (String fileName) {return imageFileEditor.openFile (fileName) + "at:" + timeLogger.getTime (); } // yderligere billedfilmetoder 

I dette tilfælde er TimeLogger klassen tager to yderligere tjenester, SimpleDateFormat og Kalender:

offentlig klasse TimeLogger {privat SimpleDateFormat dateFormat; privat kalenderkalender; // konstruktører offentlige String getTime () {return dateFormat.format (calendar.getTime ()); }}

Hvordan fortæller vi CDI, hvor man skal se på for at få en fuldt initialiseret TimeLogger objekt?

Vi opretter bare en TimeLogger fabriksklasse og kommentere sin fabriksmetode med @Produces kommentar:

offentlig klasse TimeLoggerFactory {@Producerer offentlig TimeLogger getTimeLogger () {returner ny TimeLogger (ny SimpleDateFormat ("HH: mm"), Calendar.getInstance ()); }}

Hver gang vi får en ImageFileProcessor CDI scanner f.eks TimeLoggerFactory klasse, så ring til getTimeLogger () metode (som den er kommenteret med @Produces kommentar), og indsprøjt endelig Tidslogger service.

Hvis vi kører den refactored prøveansøgning med Svejse, vil det sende følgende:

Åbning af PNG-fil file1.png kl: 17:46

8. Tilpassede kvalifikationer

CDI understøtter brugen af ​​brugerdefinerede kvalifikatorer til kvalificerende afhængigheder og løsning af tvetydige indsprøjtningspunkter.

Brugerdefinerede kvalifikationer er en meget kraftig funktion. De binder ikke kun et semantisk navn til en tjeneste, men de binder også metadata til injektion. Metadata såsom RetentionPolicy og de juridiske annoteringsmål (ElementType).

Lad os se, hvordan vi bruger tilpassede kvalifikationer i vores applikation:

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

Lad os nu binde de brugerdefinerede kvalifikationer til ImageFileEditor implementeringer:

@GifFileEditorQualifier offentlig klasse GifFileEditor implementerer ImageFileEditor {...} 
@JpgFileEditorQualifier offentlig klasse JpgFileEditor implementerer ImageFileEditor {...}
@PngFileEditorQualifier offentlig klasse PngFileEditor implementerer ImageFileEditor {...} 

Endelig, lad os omforme injektionsstedet i ImageFileProcessor klasse:

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

Hvis vi kører vores applikation igen, skal den generere den samme output som vist ovenfor.

Brugerdefinerede kvalifikatorer giver en pæn semantisk tilgang til binding af navne og annoteringsmetadata til implementeringer.

Ud over, brugerdefinerede kvalifikatorer giver os mulighed for at definere mere restriktive typesikre indsprøjtningspunkter (bedre end funktionerne i @Default- og @Alternative-kommentarerne).

Hvis kun en undertype er kvalificeret i et typehierarki, vil CDI kun indsprøjte undertypen, ikke basistypen.

9. Konklusion

Utvivlsomt, CDI gør afhængighedsinjektion til en no-brainer, omkostningerne ved de ekstra annoteringer er meget lille indsats for at få organiseret afhængighedsinjektion.

Der er tidspunkter, hvor DYDI stadig har sin plads over CDI. Ligesom når man udvikler ret enkle applikationer, der kun indeholder enkle objektgrafer.

Som altid er alle kodeeksempler vist i denne artikel tilgængelige på GitHub.


$config[zx-auto] not found$config[zx-overlay] not found