Uvod u Spring IoC
Uvod u Spring IoC
Bez dugog uvoda, danas ću te za minutu provesti kroz Spring IoC. Spring framework je jedan od najvažnijih osnovnih frameworka u Java backend razvoju, moglo bi se reći nezamenljiv, a IoC je jedan od dva najvažnija koncepta u Spring ekosistemu (ko je onaj drugi?).

Šta je
Nekoliko najvažnijih projekata iz Spring porodice zasnovano je na Spring Framework-u, pa ćemo uzeti Spring Framework kao primer i pregledati dokumentaciju.
Prvo, sa desne strane postoji link ka GitHub-u, a ako kliknemo na „LEARN", videćemo dokumentaciju za različite verzije.

Kada kliknemo na „Reference Doc", videćemo opise pojedinih modula:

Prvo poglavlje, Overview, govori o istoriji, dizajn principima i sl.;
Drugo poglavlje, Core, sadrži IoC kontejner, AOP i sl., i naravno govori o jezgru Spring-a, pa treba dobro pregledati.

Kada uđemo, nalazimo dragocene materijale za učenje; sva pitanja šta, zašto i kako mogu se ovde naći.

Ovde je vrlo dobro objašnjen čuveni IoC - Inversion of Control, odnosno inverzija kontrole.
Da rezimiram ukratko: inverzija kontrole znači da se proces kreiranja i upravljanja bean-ovima prepušta trećoj strani. A ta treća strana je upravo Spring IoC Container, i za IoC je najvažniji kontejner.
Kontejner je odgovoran za kreiranje, konfigurisanje i upravljanje bean-ovima, drugim rečima upravlja životnim ciklusom bean-a i kontroliše ubrizgavanje zavisnosti bean-a.
Prosto rečeno, pošto je u projektu svako kreiranje objekta prilično naporno, koristimo Spring IoC kontejner da upravlja tim objektima; kada ti zatreba, samo ga koristiš, ne mariš kako je nastao niti kada treba da bude uništen, samo ga koristi.
Primer: roditelji nemaju vremena da se bave decom, pa povere mališana vrtiću i mirno odu na posao ne brinući o detetu. Vrtić je taj treći kontejner, zadužen za ishranu, piće i igru deteta; roditelj je poput programera, samo dovodi i odvodi dete, a ne brine o njegovoj ishrani.
Čekaj, šta je zapravo bean?
Bean je zapravo umotan (wrapped) Object. Bilo da je reč o inverziji kontrole ili ubrizgavanju zavisnosti, subjekat je uvek object, a bean je object koji je treća strana umotala za nas (seti se kako ti drugi uvek lepo umotaju poklon koji ti daju, dok oni koje sam napraviš to ne zahtevaju).
Bean je glavni lik Spring-a; postoji izreka da je Spring zapravo programiranje orijentisano na bean (Bean Oriented Programming, BOP).
IoC kontejner
Pošto smo rekli da je kontejner najvažniji deo IoC-a, kako ga onda Spring dizajnira? Vratimo se zvaničnom sajtu, u drugom pasusu se objašnjava:

Odgovor: koristi se ApplicationContext, koji je podklasa BeanFactory-ja, i bolje dopunjuje i implementira BeanFactory.
BeanFactory je prosta i gruba, može se zamisliti kao HashMap:
- Key - bean name
- Value - bean object
Ali ona uglavnom ima samo get i put, pa se zove „niski kontejner".
Zato što ApplicationContext nasleđuje više interfejsa, ima mnogo više funkcija i može se nazvati „visoki kontejner". U projektu koji ćemo kasnije podići, koristićemo baš njega.

ApplicationContext ima dve konkretne implementacione podklase za čitanje konfiguracionih fajlova:
ClassPathXmlApplicationContext- učitava konfiguracioni fajl iz class path-a, češće se koristi;FileSystemXmlApplicationContext- učitava konfiguracioni fajl sa lokalnog fajl sistema, ređe se koristi, jer u Linux okruženju ponovo treba menjati putanje, što nije preterano zgodno.
Kada otvorimo ClassPathXmlApplicationContext, vidimo da on ne nasleđuje direktno ApplicationContext, već ima više slojeva zavisnosti; svaki pod-sloj dopunjuje implementaciju svog roditelja.
Ako se penjemo još više, vidimo da najviši class ponovo vodi ka BeanFactory, pa je on zato veoma važan.
Treba obratiti pažnju da Spring ima i FactoryBean; ova dva nemaju poseban odnos, samo su im imena slična, pa ih ne treba pomešati.
Da bismo lakše razumeli IoC, podsetimo se kako se pisao kod bez IoC-a.
Dublje razumevanje IoC-a
Koristićemo klasičan class Rectangle kao primer:
- dve promenljive: dužina i širina
- automatski generisati
set()metod itoString()metod
Napomena ⚠️: obavezno generiši set() metod, jer Spring IoC ubrizgava upravo preko tog set() metoda; toString() služi da bismo lakše ispisali i pregledali.
public class Rectangle {
private int width;
private int length;
public Rectangle() {
System.out.println("Hello World!");
}
public void setWidth(int widTth) {
this.width = widTth;
}
public void setLength(int length) {
this.length = length;
}
@Override
public String toString() {
return "Rectangle{" +
"width=" + width +
", length=" + length +
'}';
}
}Zatim u test fajlu ručno, preko set() metoda, dodeljujemo vrednosti promenljivama.
Hm, ovo je zapravo proces „dekuplovanja"!
public class MyTest {
@Test
public void myTest() {
Rectangle rect = new Rectangle();
rect.setLength(2);
rect.setWidth(3);
System.out.println(rect);
}
}Ovo je u suštini način na koji IoC dodeljuje vrednosti svojstvima: proces „kreiranja objekta" poveravamo set() metodu, umesto da sami radimo new, pa objekat više ne pravimo sami.
Ovde pod „sami kreiramo" mislim na direktno new unutar samog objekta, dakle aktivni proces u kom program sam stvara objekte; kada koristimo set() metod, to nam neko drugi (test) daje; a IoC svojim kontejnerom kreira i upravlja tim objektima, zapravo takođe koristi taj set() metod. Ne veruješ? Ukloni ili preimenuj taj metod pa isprobaj.
Nekoliko ključnih pitanja:
Šta je kontrola, šta se kontroliše?
Odgovor: pravo na kreiranje i upravljanje bean-om, kontrola nad celokupnim životnim ciklusom bean-a.
Šta je inverzija, šta je invertovano?
Odgovor: prepuštanje tog prava Spring kontejneru, umesto da sami kontrolišemo, jeste inverzija. Prelaz sa „sam aktivno kreiram objekat" na „pasivno primam objekat od drugog" - to je inverzija.
Primer iz života: aktivno i pasivno investiranje.
Ko sam trguje i bira deznice jeste aktivan investitor, inicijativa je u njegovim rukama; a ko kupi investicioni fond jeste pasivan investitor - inicijativu prepusta menadžeru fonda, i osim ako ne proda taj fond, menadžer fonda odlučuje koje će investicione proizvode birati.
Ubrizgavanje zavisnosti
Vratimo se dokumentaciji; u drugoj rečenici kaže: IoC is also known as DI.
Da porazgovaramo o dependency injection - ubrizgavanju zavisnosti.
Šta je zavisnost, čega se zavisi?
Program za rad zavisi od spoljnih resursa, koji obezbeđuju podatke i resurse potrebne objektima unutar programa.
Šta je ubrizgavanje, šta se ubrizgava?
Konfiguracioni fajl ubrizgava resurse spolja u unutrašnjost; kontejner učita spoljne fajlove, objekte i podatke, a zatim ih ubrizgava objektima unutar programa, čime održava zavisnosti između unutrašnjih i spoljnih objekata.
Dakle, inverzija kontrole se ostvaruje putem ubrizgavanja zavisnosti. Ali dobro promisli - postoji razlika, kao da se radi o »isto stvar opisana iz različitih uglova«:
- IoC je dizajnerska ideja, DI je konkretna implementacija;
- IoC je teorija, DI je praksa;
Tako se postiže dekuplovanje među objektima.
Naravno, IoC se može ostvariti i drugim putem, a DI je samo izbor Spring-a.
Ni IoC ni DI nisu smišljeni u Spring frameworku; Spring ih je samo primenio u svoj framework.
Zašto
Zašto onda uopšte koristiti ideju IoC-a? Drugim rečima, koje koristi nam IoC donosi?
Odgovor: dekuplovanje.
On zavisnosti među objektima prebacuje na upravljanje putem konfiguracionog fajla, i to poverava Spring IoC Containeru.
U projektu, osnovna implementacija sastoji se od mnoštva objekata koji sarađuju kako bi ostvarili poslovnu logiku. Ali kad mnogo objekata čvrsto povežemo, čim jedan zakomi, neminovno utiče na ostale, pa je iz tog razloga nastala ideja dekuplovanja.


Kao na slicji, ABCD su prvobitno bili međusobno povezani; kada se ubaci upravljanje trećeg kontejnera, svaki objekat se vezuje za IoC kontejner te treće strane, međusobno više nisu direktno povezani, nema više sprega, i svim objektima upravlja kontejner; time se smanjuje međusobna bliskost objekata, i to zovemo „dekuplovanje".
Kako
Konačno, deo za praksu: hajdemo zaista da podignemo Spring projekat i isprobamo IoC.
Danas se uglavnom koristi maven za izgradnju projekata, što olakšava upravljanje jar paketima; ali ja ću prvo objasniti proces ručnog uvoza jar paketa, jer ćemo usput naići na mnoge probleme - a to su sjajne prilike za učenje.
Pre nego što krenemo, pogledajmo sledeću sliku - čuveni dijagram Spring modula.
Osam modula Spring Framework-a

Modularnost je izuzetno važna ideja u Spring-u.
Spring framework je slojevita arhitektura; svaki modul može da se koristi samostalno ili u sprezi sa drugim modulima.
Svaki „zeleni okvir" odgovara jednom modulu - ukupno ih je 8; „crna pakovanja" označavaju jar pakete potrebne za implementaciju tog modula.
Core Container, koji smo već videli u dokumentaciji, jeste IoC kontejner, jezgro celog sistema, i vidi se da zavisi od ova 4 jar paketa:
BeansCoreContextSpEL, spring expression language
Ovde saznajemo da, ako želimo IoC, moramo uvesti ta 4 jar paketa. Među njima, Core modul je srž Spring-a; sve funkcije Spring-a zavise od tog jar paketa, a Core uglavnom implementira IoC, tako da - otvoreno rečeno - sve funkcije Spring-a počivaju na IoC-u.
Ostali moduli nisu usko vezani za ovaj tekst, pa ih nećemo razmatrati.
Kada podižemo Spring projekat, naravno da možemo uvesti sve jar pakete, ali da li tvoj kompjuter to izdržava... Što je paket veći, to je projekat veći, a problema više, pa zato biraj po potrebi, ne gomilaj.
Usput, ova slika se pojavljuje po celom internetu, ali je ja nisam uspeo da nađem u najnovijem reference doc-u... Ipak, pošto se pojavljuje u starijim tutorijalima, znači da je bila u starim verzijama dokumentacije, pa da potrazimo u starim verzijama 😂
Na prvoj slicji ovog teksta, Spring Framework - Documentation, izabraćemo 4.3.26 Reference Doc., a zatim pretražiti „Framework Modules" i eto ga~ Konkretan link se nalazi u referencama na kraju teksta.
Postoji još jedan način, o kom će biti reči kad dođemo do sadržaja jar paketa.
Podizanje Spring projekta
Sad kad znamo koje jar pakete treba uvesti, krenimo da ih tražimo 😂.
Jedan: način ručnog učitavanja jar paketa
1. Preuzimanje
Adresa za preuzimanje:

Ako me pitaš kako sam ga našao, evo: iz istog 4.3.26 Reference Doc-a, čim na početku stoji Distribution Zip Files,

radoznalost me natera da ga otvorim, i gle šta sam našao...

našao sam adresu repozitorijuma!
Kada ga otvorim, vidim jar pakete za različite verzije~
Tražimo verziju 5.2.3, koja je na samom dnu:

I onda možemo veselo da ih koristimo~
Dist.zipsu jar paketiDocs.zipje dokumentacija
Ostalo za sada ne diramo~
Kada preuzmemo, detaljno pregledajmo ovaj veliki poklon koji nam Spring daje.
Ovim odgovaramo na pitanje iz ranijeg teksta: gde naći dijagram Spring Framework-a.
Odgovor je: preuzeti docs.zip → spring-framework-reference → images → spring-overview

Gde su jar paketi koje treba uvesti u IntelliJ? Dist.zip → libs

Vidimo da svaki crni okvir odgovara 3 jar paketa, a u IntelliJ treba uvesti RELEASE.jar.
2. Izgradnja projekta bez IoC-a
Napravićemo new project, bez Mavena, običan Java projekat - ja ću ga zvati Spring_HelloWorld - i ponovo ću iskoristiti svoj uobičajeni primer class Rectangle.
Zatim ćemo u External Libraries uvesti jar pakete za ona 4 modula koja smo videli na dijagramu modula, struktura izgleda ovako:

Da li misliš da je time sve gotovo? Too young too simple~
Pokreni ga:

Pojavljuje se stari drugar: no class def found error, što znači da se ta klasa ne nalazi.
Guglujemo Maven common logging, preuzmemo njen jar paket i dodamo ga u projekat.
Na mojoj slici već je dodat, pa ćeš videti commons-logging-1.2.
Pokreni ponovo i radiće. Ova dva fajla su prikazana na slikama iznad.
Za sada smo ručno, preko set() metoda, postavljali objekte; kako onda da koristimo Spring IoC?
3. Detaljno o konfiguracionom fajlu Spring IoC-a
Potreban je i konfiguracioni fajl, ali šta u njemu podesiti i kako?
Zvanični sajt nam je sve napisao:

Prvi pasus daje uvod u namespace-ove i njihove specifikacije,
a drugi pasus dodeljuje vrednosti svojstvima bean-a.
Ovde obrati pažnju da konfiguraciju unutar bean treba izmeniti tako da odgovara našem projektu. Šta znače id i class? Objašnjenje postoji i na zvaničnom sajtu, a ja ću ukratko:
beanoznaka: govori Springu koji objekat da kreiraid: jedinstveni identifikator objekta, kao lična karta, ne sme se ponavljaticlass: potpuno kvalifikovano imebean-a, od package name do class nameproperty: dodeljuje vrednost svojstvu, imenamezavisi od parametraset()metoda;
Vrednosti se mogu dodeljivati i preko konstruktora, pri čemu ime name zavisi od liste parametara; više o dodeljivanju složenih tipova podataka može se naći na zvaničnom sajtu.
Naravno, u radu su mnogo češće u upotrebi anotacije. Ali se često zajedno koristi i xml fajl, pa je potrebno razumeti i to.
Moja service konfiguracija izgleda ovako:

4. Poslednji korak - da vidimo kako se koristi:

Ovde nigde nije direktno urađen new tog service-a, ali je Spring kontejner kreirao taj objekat za nas.
Kako nas Spring pri tome spasava?
ApplicationContext je ulaz u IoC kontejner, u suštini ulaz u Spring program, a već su pomenuta njegova dva konkretna implementaciona podklasa; ovde se koristi način čitanja iz class path-a;
druga linija dohvata konkretan bean. Postoji više načina, videćeš već pri korišćenju:

Kada uđemo, vidimo da je definisano u BeanFactory.class:

Među njima se najčešće koristi dohvatanje preko
- Id → zahteva cast
- Tip Bean-a → koristi se samo kod Singletona, inače nije jasno koji se uzima
- Id + tip → primer u sledećoj slici
za dohvatanje objekta, dok se poslednja dva - String, Class objects sa promenljivim brojem argumenata - retko koriste.
Po uzoru na to, moj test fajl izgleda ovako:

Uspešno pokrenuto~~🎉🎉
Follow up 1. Objekti su u kontejneru podrazumevano singletoni
Praksa je jedini kriterijum istine:
Još jednom dohvati objekat preko getBean() i ispitaj da li je u pitanju isti.
Dakle:
public class MyTest {
public void test myTest() {
ApplicationContext context = new ClassPathXmlApplicationContext("service.xml");
Rectangle rect = context.getBean("rectangle", Rectangle.class);
Rectangle rect2 = context.getBean("rectangle", Rectangle.class);
System.out.println(rect == rect2);
}
}
}Vraća True ili False?
Odgovor: True
Jer je podrazumevano singleton; ako želiš da promeniš, u konfiguracionom fajlu stavi <bean … scope = "prototype">.
O upotrebi ovih oznaka ovde nećemo dalje.
Follow up 2. Kada se kreiraju objekti u kontejneru?
Praksa je jedini kriterijum istine:
Definiši bezargumenski konstruktor koji ispiše jednu rečenicu, a zatim samo uradi new ClassPathXmlApplicationContext, kao na slici:

Vidimo da se i tada ispisuje, što znači da se zapravo pri svakom pokretanju kontejnera već kreiraju svi objekti u kontejneru. (Naravno, to ne važi za scope = "prototype", već samo za singleton.)
Da dodam - najbolje je uvek zadržati jedan bezargumenski konstruktor, jer se ovde kreiranje bean objekta obavlja refleksijom,
clazz.newInstance()podrazumevano poziva bezargumenski konstruktor
mada je sada zastarelo i zamenjeno je sledećim:
clazz.getDeclaredConstructor().newInstance()
Dva: izgradnja projekta pomoću Maven-a
Vratimo se na sam početak izgradnje projekta. Verujem da si osetio napornost ručnog uvoza jar paketa; zapravo, za upravljanje jar paketima u projektu možemo koristiti Maven, što je u firmama prilično čest pristup i oslobađa nas ručnog preuzimanja jar paketa.
1. Novi projekat
Sa Mavenom je mnogo jednostavnije: prvo ćemo napraviti Maven projekat, a razlika u odnosu na prethodni proces je u tome što:
pri New Project treba izabrati izgradnju iz Maven-a, umesto običnog Java projekta.

Pošto ga kreiramo, vidimo da u poređenju sa prethodnim Java projektom imamo više toga:
Za razliku od prethodnog praznog projekta, ovde postoje main i test, pri čemu resources služi za konfiguracione fajlove - dakle tamo treba staviti naš service.xml; ako ga ne smestimo na pravo mesto, kod ga neće naći~

2. Dodaj odgovarajuće pom zavisnosti, pa nema ručnog uvoza jar paketa
Adresa repozitorijuma
https://mvnrepository.com/Pretraži
springIzaberi
Spring context→5.2.3 release, prekopiraj konfiguraciju upom.xml
Na kraju će se u levom panelu external libraries automatski pojaviti potrebni paketi - jednim klikom, ne može zgodnije~
3. Piši kod~~🎉🎉
Kratak pregled
Da još jednom doživimo proces kreiranja objekata pomoću Spring-a:
preko ApplicationContext, ulaza u taj IoC kontejner, koristimo njegova dva konkretna implementaciona podklasa, čitamo podatke iz class path-a ili file path-a, a zatim pomoću getBean() dohvatamo konkretnu bean instancu.
Čiji je to posao zapravo oslobodila upotreba Spring-a?
Odgovor: proces new. Prepuštanje procesa new trećoj strani na kreiranje i upravljanje - to je „dekuplovanje".

I Spring koristi set() metod; on samo pruža potpuniji mehanizam implementacije.
A kada se zaviri u srž, principi nisu previše složeni; samo da bi poboljšao proširivost i kompatibilnost, Spring pruža bogatu podršku, pa zato deluje da je izvorni kod težak.
Framework mora da posluži različite korisnike, pa se više brine o proširivosti. Da mi to implementiramo, možda bismo se snašli u tri-pet linija, ali naše rešenje ne bi bilo ni potpuno, ni precizno, ni temeljno - jednom rečju, ne bi bilo „visokog nivoa"; zato on piše trideset-pedeset linija, dizajnira framework što potpunije, pruža bogatu podršku i time zadovoljava potrebe različitih korisnika - a to je i način da osvoji veće tržište.
Autor: Xiaoqi, link ka originalu: https://mp.weixin.qq.com/s/CcL3cEcQRi-KhwTwmf5A0w
