Uvod u CDI (konteksti i ubrizgavanje ovisnosti) u Javi

1. Pregled

CDI (Contexts and Dependency Injection) je standardni okvir za ubrizgavanje ovisnosti uključen u Java EE 6 i novije verzije.

Omogućuje nam upravljanje životnim ciklusom komponenata sa statusom putem konteksta životnog ciklusa specifičnih za domenu i ubrizgavanje komponenata (usluga) u klijentske objekte na siguran način.

U ovom uputstvu detaljno ćemo pogledati najrelevantnije značajke CDI-a i primijeniti različite pristupe ubrizgavanju ovisnosti u klase klijenta.

2. DYDI (Ubrizgavanje ovisnosti "uradi sam")

Ukratko, moguće je implementirati DI bez pribjegavanja bilo kojem okviru.

Ovaj je pristup u narodu poznat pod nazivom DYDI (Uradi samostalno ubrizgavanje ovisnosti).

S DYDI-jem držimo kod aplikacije izoliranim od stvaranja objekta prosljeđivanjem potrebnih ovisnosti u klase klijenta kroz obične stare tvornice / graditelje.

Evo kako bi mogla izgledati osnovna implementacija DYDI-a:

javno sučelje TextService {String doSomethingWithText (Tekst niza); String doSomethingElseWithText (tekst niza); }
javna klasa SpecializedTextService implementira TextService {...}
javna klasa TextClass {private TextService textService; // konstruktor}
javna klasa TextClassFactory {public TextClass getTextClass () {return new TextClass (new SpecializedTextService ();}}

Naravno, DYDI je pogodan za neke relativno jednostavne slučajeve uporabe.

Ako bi naša aplikacija s uzorkom rasla u veličini i složenosti, implementirajući veću mrežu međusobno povezanih objekata, na kraju bismo je zagađivali tonama tvornica grafova objekata.

To bi zahtijevalo puno uzorka koda samo za stvaranje grafova objekata. Ovo nije potpuno skalabilno rješenje.

Možemo li napraviti DI bolje? Naravno da možemo. Evo točno gdje CDI dolazi u sliku.

3. Jednostavan primjer

CDI pretvara DI u nesmetan postupak, sveden na samo ukrašavanje klasa usluga s nekoliko jednostavnih napomena i definiranje odgovarajućih točaka ubrizgavanja u klijentskim klasama.

Da bismo prikazali kako CDI implementira DI na najosnovnijoj razini, pretpostavimo da želimo razviti jednostavnu aplikaciju za uređivanje slikovnih datoteka. Sposobnost otvaranja, uređivanja, pisanja, spremanja slikovne datoteke i tako dalje.

3.1. The "Grah.xml" Datoteka

Prvo, moramo postaviti a "Grah.xml" datoteku u “Src / main / resources / META-INF /” mapu. Čak i ako ova datoteka uopće ne sadrži posebne DI direktive, potrebna je za pokretanje i pokretanje CDI-ja:

3.2. Uslužne klase

Dalje, kreirajmo klase usluga koje izvode gore spomenute datoteke na GIF, JPG i PNG datotekama:

javno sučelje ImageFileEditor {String openFile (Niz datotekeName); String editFile (String fileName); String writeFile (String fileName); String saveFile (Niz datotekeName); }
javna klasa GifFileEditor implementira ImageFileEditor {@Override public String openFile (String fileName) {return "Otvaranje GIF datoteke" + fileName; } @Override javni niz editFile (Niz datotekeName) {return "Uređivanje GIF datoteke" + Ime datoteke; } @Override javni String writeFile (Niz datotekeName) {return "Zapisivanje GIF datoteke" + Ime datoteke; } @Override javni String saveFile (Niz datotekeName) {return "Spremanje GIF datoteke" + Ime datoteke; }}
javna klasa JpgFileEditor implementira ImageFileEditor {// JPG-specifične implementacije za openFile () / editFile () / writeFile () / saveFile () ...}
javna klasa PngFileEditor implementira ImageFileEditor {// implementacije specifične za PNG za openFile () / editFile () / writeFile () / saveFile () ...}

3.3. Klasa klijenta

Napokon, implementiramo klijentsku klasu koja uzima ImageFileEditor implementaciju u konstruktoru i definirajmo mjesto ubrizgavanja s @Ubrizgati napomena:

javna klasa ImageFileProcessor {private ImageFileEditor imageFileEditor; @ Ubrizgajte javni ImageFileProcessor (ImageFileEditor imageFileEditor) {this.imageFileEditor = imageFileEditor; }}

Jednostavno rečeno, @Ubrizgati napomena je stvarni CDI-jev radni konj. Omogućuje nam definiranje mjesta ubrizgavanja u klijentskim klasama.

U ovom slučaju, @Ubrizgati upućuje CDI da ubrizga ImageFileEditor implementacija u konstruktoru.

Nadalje, također je moguće ubrizgati uslugu pomoću @Ubrizgati napomena u poljima (ubrizgavanje polja) i postavljačima (ubrizgavanje postavljača). Ove ćemo opcije pogledati kasnije.

3.4. Izgradnja ImageFileProcessor Grafikon predmeta sa zavarom

Naravno, moramo biti sigurni da će CDI ubrizgati pravo ImageFileEditor implementacija u ImageFileProcessor konstruktor klase.

Da bismo to učinili, prvo bismo trebali dobiti primjerak klase.

Kako se nećemo oslanjati ni na jedan Java EE aplikacijski poslužitelj za upotrebu CDI-a, to ćemo učiniti s Weldom, CDI referentnom implementacijom u Java SE:

javna statička praznina main (String [] args) {Weld weld = new Weld (); Kontejner WeldContainer = weld.initialize (); ImageFileProcessor imageFileProcessor = container.select (ImageFileProcessor.class) .get (); System.out.println (imageFileProcessor.openFile ("file1.png")); container.shutdown (); } 

Ovdje stvaramo WeldContainer objekt, a zatim dobivanje ImageFileProcessor objekt, i konačno poziva svoj otvorena datoteka() metoda.

Kao što se i očekivalo, ako pokrenemo aplikaciju, CDI će se glasno žaliti bacajući DeploymentException:

Nezadovoljne ovisnosti za tip ImageFileEditor s kvalifikatorima @Default na mjestu ubrizgavanja ...

Dobivamo ovu iznimku jer CDI ne zna što ImageFileEditor implementacija za ubrizgavanje u ImageFileProcessor konstruktor.

U terminologiji CDI-a, ovo je poznato kao dvosmislena iznimka ubrizgavanja.

3.5. The @Zadano i @Alternativa Bilješke

Rješavanje ove dvosmislenosti je jednostavno. CDI, prema zadanim postavkama, bilježi sve implementacije sučelja s @Zadano bilješka.

Dakle, trebali bismo mu izričito reći koju implementaciju treba ubrizgati u klasu klijenta:

@Alternative public class GifFileEditor implementira ImageFileEditor {...}
@Alternative public class JpgFileEditor implementira ImageFileEditor {...} 
javna klasa PngFileEditor implementira ImageFileEditor {...}

U ovom smo slučaju napomenuli GifFileEditor i JpgFileEditor s @Alternativa napomena, tako da CDI to sada zna PngFileEditor (označeno prema zadanim postavkama s @Zadano anotacija) je implementacija koju želimo ubrizgati.

Ako ponovno pokrenemo aplikaciju, ovaj put će se izvršiti kako se očekivalo:

Otvaranje PNG datoteke file1.png 

Nadalje, bilježeći PngFileEditor s @Zadano anotacija i zadržavanje ostalih implementacija kao alternativnih rezultata rezultirat će istim gornjim rezultatom.

To ukratko pokazuje: kako možemo vrlo lako zamijeniti uvođenje implementacije tijekom izvođenja jednostavnim prebacivanjem @Alternativa napomene u uslužnim razredima.

4. Ubrizgavanje polja

CDI podržava ubrizgavanje polja i postavljača iz kutije.

Evo kako izvesti ubrizgavanje na terenu (pravila za kvalificirane usluge s @Zadano i @Alternativa napomene ostaju iste):

@Inject privatni konačni ImageFileEditor imageFileEditor;

5. Injekcija Settera

Slično tome, evo kako izvršiti ubrizgavanje setera:

@ Ubrizgavanje javne praznine setImageFileEditor (ImageFileEditor imageFileEditor) {...}

6. The @Name Bilješka

Do sada smo naučili kako definirati točke ubrizgavanja u klijentskim klasama i ubrizgati usluge s @Ubrizgati, @Zadano , i @Alternativa napomene koje pokrivaju većinu slučajeva korištenja.

Unatoč tome, CDI nam također omogućuje da izvršimo ubrizgavanje usluge s @Name bilješka.

Ova metoda pruža semantičniji način ubrizgavanja usluga, vezivanjem smislenog naziva za implementaciju:

@Named ("GifFileEditor") javna klasa GifFileEditor implementira ImageFileEditor {...} @Named ("JpgFileEditor") javna klasa JpgFileEditor implementira ImageFileEditor {...} @Named ("PngFileEditor") javna klasa PngFileEditor implementira ... }

Sada bismo trebali refaktorizirati mjesto ubrizgavanja u ImageFileProcessor klasa kako bi odgovarala imenovanoj implementaciji:

@ Ubrizgajte javni ImageFileProcessor (@Named ("PngFileEditor") ImageFileEditor imageFileEditor) {...}

Također je moguće izvesti ubrizgavanje polja i postavljača s imenovanim implementacijama, što izgleda vrlo slično korištenju @Zadano i @Alternativa napomene:

@Inject privatni konačni @Named ("PngFileEditor") ImageFileEditor imageFileEditor; @Inject public void setImageFileEditor (@Named ("PngFileEditor") ImageFileEditor imageFileEditor) {...}

7. The @Proizvodi Bilješka

Ponekad usluga zahtijeva da se neka konfiguracija potpuno inicijalizira prije nego što se ubrizga radi obrade dodatnih ovisnosti.

CDI pruža podršku za ove situacije putem @Proizvodi bilješka.

@Proizvodi omogućuje nam implementaciju tvorničkih klasa čija je odgovornost stvaranje potpuno inicijaliziranih usluga.

Da bih razumio kako @Proizvodi anotacija radi, preoblikujmo ImageFileProcessor razreda, tako da može potrajati dodatni TimeLogger usluga u konstruktoru.

Usluga će se koristiti za bilježenje vremena u kojem se izvodi određena operacija slikovne datoteke:

@ Ubrizgavanje javnog ImageFileProcessor (ImageFileEditor imageFileEditor, TimeLogger timeLogger) {...} javni niz openFile (StringName datoteke) {return imageFileEditor.openFile (fileName) + "na:" + timeLogger.getTime (); } // // dodatne metode slikovnih datoteka 

U ovom slučaju, TimeLogger klasa uzima dvije dodatne usluge, SimpleDateFormat i Kalendar:

javna klasa TimeLogger {private SimpleDateFormat dateFormat; privatni kalendar Kalendar; // konstruktori public String getTime () {return dateFormat.format (calendar.getTime ()); }}

Kako CDI-u možemo reći gdje treba tražiti dobivanje potpuno inicijaliziranih podataka TimeLogger objekt?

Mi samo stvorimo TimeLogger tvorničku klasu i označite njezinu tvorničku metodu s @Proizvodi napomena:

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

Kad god dobijemo ImageFileProcessor primjerice, CDI će skenirati TimeLoggerFactory razreda, a zatim nazovite getTimeLogger () metoda (jer je označena s @Proizvodi napomena) i na kraju ubrizgajte Dnevnik vremena servis.

Ako pokrenemo aplikaciju za refaktorirani uzorak s Zavarite, ispisat će sljedeće:

Otvaranje PNG datoteke file1.png u: 17:46

8. Prilagođene kvalifikacije

CDI podržava upotrebu prilagođenih kvalifikatora za kvalificiranje ovisnosti i rješavanje dvosmislenih točaka ubrizgavanja.

Prilagođeni kvalifikatori vrlo su moćna značajka. Oni ne samo da vezuju semantički naziv za uslugu, nego također vežu i metapodatke ubrizgavanja. Metapodaci kao što su RetentionPolicy i pravne napomene (ElementType).

Pogledajmo kako koristiti prilagođene kvalifikatore u našoj aplikaciji:

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

Sada, povežimo prilagođene kvalifikatore s ImageFileEditor implementacije:

@GifFileEditorQualifier javna klasa GifFileEditor implementira ImageFileEditor {...} 
@JpgFileEditorQualifier javna klasa JpgFileEditor implementira ImageFileEditor {...}
@PngFileEditorQualifier javna klasa PngFileEditor implementira ImageFileEditor {...} 

Na kraju, preoblikujmo točku ubrizgavanja u ImageFileProcessor razred:

@ Ubrizgavanje javnog ImageFileProcessor (@PngFileEditorQualifier ImageFileEditor imageFileEditor, TimeLogger timeLogger) {...} 

Ako još jednom pokrenemo našu aplikaciju, trebala bi generirati isti izlaz prikazan gore.

Prilagođeni kvalifikatori pružaju uredan semantički pristup vezivanju imena i metapodataka napomena za implementacije.

U Dodatku, prilagođeni kvalifikatori omogućuju nam definiranje restriktivnijih točaka ubrizgavanja sigurnih za tip (nadmašujući funkcionalnost napomena @Default i @Alternative).

Ako je samo podtip kvalificiran u hijerarhiji tipova, tada će CDI ubrizgati samo podtip, a ne osnovni tip.

9. Zaključak

Neupitno, CDI injekciju ovisnosti čini nesmetanom, trošak dodatnih bilješki vrlo je mali napor za postizanje ubrizgavanja organizirane ovisnosti.

Postoje slučajevi kada DYDI i dalje ima svoje mjesto nad CDI-jem. Kao kad razvijate prilično jednostavne aplikacije koje sadrže samo jednostavne grafove objekata.

Kao i uvijek, svi uzorci koda prikazani u ovom članku dostupni su na GitHubu.