Úvod do CDI (kontexty a závislosť vstrekovania) v Jave

1. Prehľad

CDI (Contexts and Dependency Injection) je štandardný rámec pre injektovanie závislostí zahrnutý v Java EE 6 a vyšších.

Umožňuje nám to riadiť životný cyklus stavových komponentov prostredníctvom kontextov životného cyklu špecifických pre doménu a injektovať komponenty (služby) do objektov klienta bezpečným spôsobom.

V tomto tutoriáli sa podrobne pozrieme na najdôležitejšie funkcie CDI a implementujeme rôzne prístupy pre vkladanie závislostí do tried klientov.

2. DYDI (injekcia závislosti od domácich majstrov)

Stručne povedané, je možné implementovať DI bez toho, aby ste sa uchýlili k nejakému rámci.

Tento prístup je ľudovo známy ako DYDI (Do-it-Yourself Dependency Injection).

S DYDI udržujeme aplikačný kód izolovaný od vytvárania objektov odovzdávaním požadovaných závislostí do tried klientov prostredníctvom obyčajných starých tovární / staviteľov.

Tu môže vyzerať základná implementácia DYDI:

verejné rozhranie TextService {String doSomethingWithText (String text); Reťazec doSomethingElseWithText (text reťazca); }
verejná trieda SpecializedTextService implementuje TextService {...}
verejná trieda TextClass {súkromná TextService textService; // konštruktor}
public class TextClassFactory {public TextClass getTextClass () {return new TextClass (new SpecializedTextService ();}}

DYDI je samozrejme vhodný pre niektoré pomerne jednoduché prípady použitia.

Ak by naša vzorová aplikácia narástla do veľkosti a zložitosti a implementovala by väčšiu sieť vzájomne prepojených objektov, skončilo by to jej znečistením tonami tovární na objektové grafy.

To by vyžadovalo veľa štandardného kódu iba na vytváranie grafov objektov. Toto nie je úplne škálovateľné riešenie.

Môžeme urobiť DI lepšie? Samozrejme, že môžeme. Presne tu sa objaví CDI.

3. Jednoduchý príklad

CDI premieňa DI na proces, pri ktorom nie je potrebné uvažovať, zostúpilo iba do zdobenia tried služieb niekoľkými jednoduchými anotáciami a definovania zodpovedajúcich injekčných bodov v triedach klientov.

Na ukážku toho, ako CDI implementuje DI na najzákladnejšej úrovni, predpokladajme, že chceme vyvinúť jednoduchú aplikáciu na úpravu obrazových súborov. Schopné otvárať, upravovať, písať, ukladať obrazové súbory atď.

3.1. The „Beans.xml“ Súbor

Najskôr musíme umiestniť a „Beans.xml“ súbor v „Src / main / resources / META-INF /“ priečinok. Aj keď tento súbor neobsahuje vôbec žiadne konkrétne smernice DI, je potrebný na spustenie a spustenie CDI:

3.2. Triedy služieb

Ďalej vytvoríme triedy služieb, ktoré vykonávajú vyššie uvedené operácie so súbormi GIF, JPG a PNG:

verejné rozhranie ImageFileEditor {String openFile (String fileName); Reťazec editFile (Reťazec fileName); Reťazec writeFile (Reťazec fileName); String saveFile (Reťazec fileName); }
verejná trieda GifFileEditor implementuje ImageFileEditor {@Override public String openFile (String fileName) {return "Opening GIF file" + fileName; } @Override public String editFile (String fileName) {return "Úpravy súboru GIF" + fileName; } @Override public String writeFile (String fileName) {return "Writing GIF file" + fileName; } @Override public String saveFile (String fileName) {return "Ukladanie súboru GIF" + fileName; }}
verejná trieda JpgFileEditor implementuje ImageFileEditor {// implementácie špecifické pre JPG pre openFile () / editFile () / writeFile () / saveFile () ...}
verejná trieda PngFileEditor implementuje ImageFileEditor {// implementácie špecifické pre PNG pre openFile () / editFile () / writeFile () / saveFile () ...}

3.3. Trieda klienta

Na záver poďme implementovať triedu klientov, ktorá trvá ImageFileEditor implementácia v konštruktore a definujme injekčný bod pomocou @Inject anotácia:

verejná trieda ImageFileProcessor {private ImageFileEditor imageFileEditor; @Inject public ImageFileProcessor (ImageFileEditor imageFileEditor) {this.imageFileEditor = imageFileEditor; }}

Jednoducho povedané @Inject anotácia je skutočným ťahúňom CDI. Umožňuje nám definovať injekčné body v triedach klientov.

V tomto prípade, @Inject dáva pokyn CDI, aby podal injekciu ImageFileEditor implementácia v konštruktore.

Ďalej je tiež možné vložiť službu pomocou @Inject anotácia v poliach (injekcia poľa) a nastavovači (vstrekovanie nastavovača). Na tieto možnosti sa pozrieme neskôr.

3.4. Budova ImageFileProcessor Objektový graf so zvarom

Samozrejme, musíme sa ubezpečiť, že CDI vstrekne správne ImageFileEditor implementácia do ImageFileProcessor konštruktor triedy.

Najskôr by sme mali získať inštanciu triedy.

Pretože sa pri používaní CDI nebudeme spoliehať na žiadny aplikačný server Java EE, urobíme to pomocou aplikácie Weld, referenčnej implementácie CDI v Java SE.:

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

Tu vytvárame Kontajner na zváranie objekt, potom dostane ImageFileProcessor objekt a nakoniec volá jeho otvorený súbor() metóda.

Podľa očakávania, ak spustíme aplikáciu, CDI sa nahlas sťažuje vyhodením a DeploymentException:

Neuspokojené závislosti pre typ ImageFileEditor s kvalifikátormi @Default v mieste vpichu ...

Dostávame túto výnimku, pretože CDI nevie čo ImageFileEditor implementácia vstreknúť do ImageFileProcessor konštruktér.

V terminológii CDI, toto je známe ako nejednoznačná výnimka pre injekcie.

3.5. The @ Predvolené a @ Alternatívne Anotácie

Riešenie tejto nejasnosti je ľahké. CDI štandardne anotuje všetky implementácie rozhrania s @ Predvolené anotácia.

Mali by sme mu teda výslovne povedať, ktorá implementácia by sa mala vložiť do triedy klientov:

@ Alternatívna verejná trieda GifFileEditor implementuje ImageFileEditor {...}
@ Alternatívna verejná trieda JpgFileEditor implementuje ImageFileEditor {...} 
verejná trieda PngFileEditor implementuje ImageFileEditor {...}

V tomto prípade sme anotovali GifFileEditor a JpgFileEditor s @ Alternatívne anotácia, takže CDI to teraz vie PngFileEditor (štandardne anotované s @ Predvolené anotácia) je implementácia, ktorú chceme vložiť.

Ak aplikáciu znova spustíme, tentokrát sa vykoná podľa očakávania:

Otvára sa súbor PNG file1.png 

Ďalej anotovanie PngFileEditor s @ Predvolené anotácia a ponechanie ostatných implementácií ako alternatív prinesie rovnaký vyššie uvedený výsledok.

To v skratke ukazuje, ako môžeme veľmi ľahko vymeniť injekciu za behu implementácií jednoduchým prepnutím @ Alternatívne anotácie v triedach služieb.

4. Vstrekovanie do poľa

CDI podporuje vstrekovanie poľa aj setra z krabice.

Tu je príklad, ako vykonať injekciu do poľa (pravidlá pre kvalifikáciu služieb s @ Predvolené a @ Alternatívne anotácie zostávajú rovnaké):

@Inject private final ImageFileEditor imageFileEditor;

5. Injekcia nastavovača

Podobne postupujte takto:

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

6. @ Menovaný Anotácia

Doteraz sme sa naučili, ako definovať injekčné body v triedach klientov a vstreknúť služby pomocou @Inject, @ Predvolené a @ Alternatívne anotácie, ktoré pokrývajú väčšinu prípadov použitia.

Napriek tomu nám CDI tiež umožňuje vykonávať servisné injekcie pomocou @ Menovaný anotácia.

Táto metóda poskytuje sémantickejší spôsob poskytovania služieb viazaním zmysluplného názvu na implementáciu:

@Named ("GifFileEditor") verejná trieda GifFileEditor implementuje ImageFileEditor {...} @Named ("JpgFileEditor") verejná trieda JpgFileEditor implementuje ImageFileEditor {...} @Named ("PngFileEditor") verejná trieda PngFileEditor implementuje ImageFileEditor {...} }

Teraz by sme mali refaktorovať miesto vpichu v ImageFileProcessor trieda, aby zodpovedala pomenovanej implementácii:

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

Je tiež možné vykonať injekciu poľa a setra s pomenovanými implementáciami, ktoré vyzerajú veľmi podobne ako pri použití @ Predvolené a @ Alternatívne anotácie:

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

7. The @Produkty Anotácia

Služba niekedy vyžaduje úplnú inicializáciu určitej konfigurácie predtým, ako sa vstrekne na spracovanie ďalších závislostí.

CDI poskytuje podporu pre tieto situácie prostredníctvom @Produkty anotácia.

@Produkty umožňuje nám implementovať továrenské triedy, ktorých zodpovednosťou je vytváranie plne inicializovaných služieb.

Aby sme pochopili, ako @Produkty anotácia funguje, poďme ju upraviť ImageFileProcessor triedy, takže to môže trvať ďalšie TimeLogger služba v konštruktéri.

Služba sa použije na zaznamenávanie času, v ktorom sa vykoná určitá operácia so súborom obrázka:

@Inject public ImageFileProcessor (ImageFileEditor imageFileEditor, TimeLogger timeLogger) {...} public String openFile (String fileName) {return imageFileEditor.openFile (fileName) + "at:" + timeLogger.getTime (); } // ďalšie metódy obrázkových súborov 

V takom prípade TimeLogger trieda berie dve ďalšie služby, SimpleDateFormat a Kalendár:

verejná trieda TimeLogger {private SimpleDateFormat dateFormat; súkromný kalendár kalendára; // konštruktory public String getTime () {return dateFormat.format (calendar.getTime ()); }}

Ako povieme CDI, kde treba hľadať, aby sme dosiahli úplnú inicializáciu TimeLogger objekt?

Jednoducho vytvoríme a TimeLogger továrenská trieda a anotácia jej továrenskej metódy pomocou @Produkty anotácia:

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

Kedykoľvek dostaneme ImageFileProcessor napríklad CDI prehľadá TimeLoggerFactory triedy, potom zavolajte na getTimeLogger () metóda (ako je anotovaná s @Produkty anotácia) a nakoniec vložte Záznamník času služby.

Ak spustíme refaktorovanú vzorovú aplikáciu s Zvar, zobrazí sa nasledovné:

Otváranie súboru PNG file1.png o: 17:46

8. Vlastné kvalifikácie

CDI podporuje použitie vlastných kvalifikátorov na kvalifikovanie závislostí a riešenie nejednoznačných bodov vstrekovania.

Vlastné kvalifikácie sú veľmi výkonnou funkciou. Viažu nielen sémantický názov na službu, ale viažu aj metadáta vstrekovania. Metadáta ako RetentionPolicy a ciele právnych anotácií (ElementType).

Pozrime sa, ako v našej aplikácii použiť vlastné kvalifikátory:

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

Teraz spojme vlastné kvalifikátory s ImageFileEditor implementácie:

@GifFileEditorQualifier verejná trieda GifFileEditor implementuje ImageFileEditor {...} 
@JpgFileEditorQualifier verejná trieda JpgFileEditor implementuje ImageFileEditor {...}
@PngFileEditorQualifier verejná trieda PngFileEditor implementuje ImageFileEditor {...} 

Na záver poďme znova upraviť dávkovacie miesto v ImageFileProcessor trieda:

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

Ak našu aplikáciu spustíme ešte raz, mala by vygenerovať rovnaký výstup, aký je uvedený vyššie.

Vlastné kvalifikátory poskytujú čistý sémantický prístup k väzbe mien a metadát anotácií k implementáciám.

Navyše, vlastné kvalifikátory nám umožňujú definovať reštriktívnejšie typovo bezpečné injekčné body (prekonávajú funkčnosť anotácií @Default a @Alternative).

Ak je v hierarchii typov kvalifikovaný iba podtyp, potom CDI vloží iba podtyp, nie základný typ.

9. Záver

Nepochybne, Vďaka CDI nie je injekcia so závislosťou samozrejmá, náklady na ďalšie anotácie sú veľmi malým úsilím pre získanie injekcie organizovanej závislosti.

Sú chvíle, kedy má DYDI stále miesto nad CDI. Rovnako ako pri vývoji pomerne jednoduchých aplikácií, ktoré obsahujú iba jednoduché objektové grafy.

Všetky vzorky kódu zobrazené v tomto článku sú ako vždy k dispozícii na GitHub.