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

1. Oversikt

CDI (Contexts and Dependency Injection) er et standardavhengighetsinjeksjonsrammeverk inkludert i Java EE 6 og høyere.

Det lar oss administrere livssyklusen til stateful komponenter via domenespesifikke livssykluskontekster og injisere komponenter (tjenester) i klientobjekter på en typesikker måte.

I denne veiledningen tar vi en grundig titt på CDIs mest relevante funksjoner og implementerer forskjellige tilnærminger for å injisere avhengigheter i klientklasser.

2. DYDI (Gjør-det-selv-avhengighetsinjeksjon)

I et nøtteskall er det mulig å implementere DI uten å bruke noen rammer i det hele tatt.

Denne tilnærmingen er populært kjent som DYDI (Gjør-det-selv-avhengighetsinjeksjon).

Med DYDI holder vi applikasjonskoden isolert fra oppretting av objekter ved å sende de nødvendige avhengighetene inn i klientklassene gjennom vanlige gamle fabrikker / byggere.

Slik kan en grunnleggende DYDI-implementering se ut:

offentlig grensesnitt TextService {String doSomethingWithText (Strengtekst); String doSomethingElseWithText (strengtekst); }
offentlig klasse SpecializedTextService implementerer TextService {...}
offentlig klasse TextClass {private TextService textService; // konstruktør}
public class TextClassFactory {public TextClass getTextClass () {return new TextClass (new SpecializedTextService ();}}

Selvfølgelig er DYDI egnet for noen relativt enkle brukstilfeller.

Hvis vår applikasjon vokste i størrelse og kompleksitet, og implementerte et større nettverk av sammenkoblede objekter, ville vi ende opp med å forurense den med tonnevis av objektgraffabrikker.

Dette vil kreve mye kjelekode bare for å lage objektgrafer. Dette er ikke en fullt skalerbar løsning.

Kan vi gjøre DI noe bedre? Selvfølgelig kan vi det. Her er nøyaktig hvor CDI kommer inn i bildet.

3. Et enkelt eksempel

CDI gjør DI til en ikke-brainer-prosess, kokt ned til å bare dekorere serviceklassene med noen få enkle merknader, og definere de tilsvarende injeksjonspunktene i klientklassene.

For å vise hvordan CDI implementerer DI på det mest grunnleggende nivået, la oss anta at vi vil utvikle et enkelt bildefileredigeringsprogram. Kan åpne, redigere, skrive, lagre en bildefil og så videre.

3.1. De “Beans.xml” Fil

Først må vi plassere en “Beans.xml” filen i “Src / main / resources / META-INF /” mappe. Selv om denne filen ikke inneholder noen spesifikke DI-direktiver i det hele tatt, er det nødvendig for å få CDI i gang:

3.2. Serviceklassene

La oss deretter opprette tjenesteklasser som utfører filen som er nevnt ovenfor, på GIF-, JPG- og PNG-filer:

offentlig grensesnitt ImageFileEditor {String openFile (String fileName); String editFile (String filnavn); String writeFile (String filnavn); String saveFile (String filnavn); }
offentlig klasse GifFileEditor implementerer ImageFileEditor {@Override public String openFile (String fileName) {return "Åpning 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-spesifikke implementeringer for openFile () / editFile () / writeFile () / saveFile () ...}
offentlig klasse PngFileEditor implementerer ImageFileEditor {// PNG-spesifikke implementeringer for openFile () / editFile () / writeFile () / saveFile () ...}

3.3. Klientklassen

Til slutt, la oss implementere en klientklasse som tar en ImageFileEditor implementering i konstruktøren, og la oss definere et injeksjonspunkt med @Injiser kommentar:

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

Enkelt sagt, den @Injiser kommentar er CDIs faktiske arbeidshest. Det lar oss definere injeksjonspunkter i klientklassene.

I dette tilfellet, @Injiser instruerer CDI om å injisere en ImageFileEditor implementering i konstruktøren.

Videre er det også mulig å injisere en tjeneste ved å bruke @Injiser merknad i felt (feltinjeksjon) og settere (setterinjeksjon). Vi ser på disse alternativene senere.

3.4. Bygge ImageFileProcessor Objektgraf med sveis

Selvfølgelig må vi sørge for at CDI vil injisere riktig ImageFileEditor implementering i ImageFileProcessor klassekonstruktør.

For å gjøre det, bør vi først få en forekomst av klassen.

Siden vi ikke vil stole på noen Java EE-applikasjonsserver for å bruke CDI, vil vi gjøre dette med Weld, CDI-referanseimplementeringen 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 lager vi en Sveisebeholder objekt, og deretter få et ImageFileProcessor objekt, og til slutt kaller det åpen fil() metode.

Som forventet, hvis vi kjører applikasjonen, vil CDI klage høyt ved å kaste a Distribusjon unntak:

Utilfredse avhengigheter for typen ImageFileEditor med kvalifiserende @ standard ved injeksjonspunkt ...

Vi får dette unntaket fordi CDI ikke vet hva ImageFileEditor implementering for å injisere i ImageFileProcessor konstruktør.

I CDIs terminologi, dette er kjent som et tvetydig injeksjons unntak.

3.5. De @Misligholde og @Alternativ Kommentarer

Det er enkelt å løse denne tvetydigheten. CDI, som standard, kommenterer alle implementeringene av et grensesnitt med @Misligholde kommentar.

Så vi bør eksplisitt fortelle det hvilken implementering som skal injiseres i klientklassen:

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

I dette tilfellet har vi kommentert GifFileEditor og JpgFileEditor med @Alternativ kommentar, så CDI vet det nå PngFileEditor (merket som standard med @Misligholde kommentar) er implementeringen vi vil injisere.

Hvis vi kjører applikasjonen på nytt, blir den denne gangen utført som forventet:

Åpne PNG-fil file1.png 

Videre kommentering PngFileEditor med @Misligholde kommentar og å beholde de andre implementeringene som alternativer, vil gi det samme resultatet ovenfor.

Dette viser, i et nøtteskall, hvordan vi veldig enkelt kan bytte kjøretidsinjeksjon av implementeringer ved å bare bytte @Alternativ merknader i serviceklassene.

4. Feltinjeksjon

CDI støtter både felt- og setterinjisering ut av esken.

Slik utfører du feltinjeksjon (reglene for kvalifiserende tjenester med @Misligholde og @Alternativ merknader forblir de samme):

@Inject private final ImageFileEditor imageFileEditor;

5. Setter Injection

På samme måte kan du gjøre setterinjeksjon:

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

6. Den @Named Kommentar

Så langt har vi lært å definere injeksjonspunkter i klientklasser og injisere tjenester med @Injiser, @Misligholde , og @Alternativ merknader, som dekker de fleste brukssakene.

Ikke desto mindre tillater CDI oss også å utføre serviceinjeksjon med @Named kommentar.

Denne metoden gir en mer semantisk måte å injisere tjenester på, ved å binde et meningsfylt 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 }

Nå skal vi omforme injeksjonspunktet i ImageFileProcessor klasse for å matche en navngitt implementering:

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

Det er også mulig å utføre felt- og setterinjeksjon med navngitte implementeringer, som ser veldig ut som å bruke @Misligholde og @Alternativ kommentarer:

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

7. Den @Produkter Kommentar

Noen ganger krever en tjeneste at noen konfigurasjoner er fullstendig initialisert før den blir injisert for å håndtere ekstra avhengigheter.

CDI gir støtte for disse situasjonene gjennom @Produkter kommentar.

@Produkter tillater oss å implementere fabrikklasser, hvis ansvar er å opprette fullinitialiserte tjenester.

For å forstå hvordan @Produkter merknader fungerer, la oss omformulere ImageFileProcessor klasse, så det kan ta en ekstra TimeLogger tjeneste i konstruktøren.

Tjenesten vil bli brukt til å logge tidspunktet da en bestemt bildefiloperasjon utføres:

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

I dette tilfellet TimeLogger klassen tar to tilleggstjenester, SimpleDateFormat og Kalender:

offentlig klasse TimeLogger {privat SimpleDateFormat dateFormat; privat kalenderkalender; // constructors public String getTime () {return dateFormat.format (calendar.getTime ()); }}

Hvordan forteller vi CDI hvor vi skal se på for å få en fullinitialisert TimeLogger gjenstand?

Vi lager bare en TimeLogger fabrikklasse og kommentere fabrikkmetoden med @Produkter kommentar:

offentlig klasse TimeLoggerFactory {@Produces public TimeLogger getTimeLogger () {return new TimeLogger (new SimpleDateFormat ("HH: mm"), Calendar.getInstance ()); }}

Hver gang vi får en ImageFileProcessor CDI vil for eksempel skanne TimeLoggerFactory klasse, og ring deretter getTimeLogger () metoden (som den er kommentert med @Produkter merknad), og til slutt injisere Time Logger service.

Hvis vi kjører den ombygde prøvesøknaden med Sveis, vil det sende følgende:

Åpner PNG-fil file1.png kl: 17:46

8. Egendefinerte kvalifikasjoner

CDI støtter bruken av tilpassede kvalifikasjoner for kvalifiserende avhengigheter og løsning av tvetydige injeksjonspunkter.

Egendefinerte kvalifikasjoner er en veldig kraftig funksjon. De binder ikke bare et semantisk navn til en tjeneste, men de binder også metadata for injeksjon. Metadata som RetentionPolicy og juridiske merknadsmål (ElementType).

La oss se hvordan du bruker tilpassede kvalifikasjoner i applikasjonen vår:

@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 {} 

La oss nå binde de tilpassede kvalifikasjonene til ImageFileEditor implementeringer:

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

Til slutt, la oss omforme injeksjonspunktet i ImageFileProcessor klasse:

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

Hvis vi kjører applikasjonen vår igjen, bør den generere den samme effekten som vist ovenfor.

Egendefinerte kvalifikasjoner gir en pen semantisk tilnærming for å binde navn og merknadsmetadata til implementeringer.

I tillegg, tilpassede kvalifikasjoner tillater oss å definere mer restriktive typesikre injeksjonspunkter (bedre enn funksjonaliteten til @Default- og @Alternative-kommentarene).

Hvis bare en undertype er kvalifisert i et typehierarki, vil CDI bare injisere undertypen, ikke basistypen.

9. Konklusjon

Utvilsomt CDI gjør avhengighetsinjeksjon til en no-brainerkoster ekstra merknader svært lite innsats for å få organisert avhengighetsinjeksjon.

Det er tider når DYDI fremdeles har sin plass over CDI. Som når du utvikler ganske enkle applikasjoner som bare inneholder enkle objektgrafer.

Som alltid er alle kodeeksemplene vist i denne artikkelen tilgjengelig på GitHub.


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