Introduktion til inversion af kontrol og afhængighedsinjektion med forår

1. Oversigt

I denne artikel introducerer vi begreberne IoC (Inversion of Control) og DI (Dependency Injection), og vi ser derefter på, hvordan disse implementeres i foråret.

2. Hvad er inversion af kontrol?

Inversion of Control er et princip inden for softwareteknik, hvorved kontrol af objekter eller dele af et program overføres til en container eller ramme. Det bruges oftest i sammenhæng med objektorienteret programmering.

I modsætning til traditionel programmering, hvor vores brugerdefinerede kode ringer til et bibliotek, giver IoC mulighed for en ramme til at tage kontrol over strømmen af ​​et program og foretage opkald til vores brugerdefinerede kode. For at aktivere dette bruger rammer abstraktioner med indbygget yderligere adfærd. Hvis vi vil tilføje vores egen adfærd, er vi nødt til at udvide klasser i rammen eller plugin vores egne klasser.

Fordelene ved denne arkitektur er:

  • afkobling af udførelsen af ​​en opgave fra dens implementering
  • hvilket gør det lettere at skifte mellem forskellige implementeringer
  • større modularitet af et program
  • større lethed i at teste et program ved at isolere en komponent eller spotte dens afhængigheder og lade komponenter kommunikere gennem kontrakter

Inversion af kontrol kan opnås gennem forskellige mekanismer såsom: Strategidesignmønster, Service Locator-mønster, Fabriksmønster og Dependency Injection (DI).

Vi skal se på DI næste.

3. Hvad er afhængighedsinjektion?

Afhængighedsinjektion er et mønster, hvor IoC implementeres, hvor den kontrol, der inverteres, er indstillingen af ​​objekts afhængigheder.

Handlingen med at forbinde objekter med andre objekter eller "injicere" objekter i andre objekter udføres af en samler snarere end af objekterne selv.

Sådan opretter du en objektafhængighed i traditionel programmering:

offentlig klasse butik {privat vare vare; public Store () {item = new ItemImpl1 (); }}

I eksemplet ovenfor er vi nødt til at starte en implementering af Vare interface inden for butik klassen selv.

Ved at bruge DI kan vi omskrive eksemplet uden at specificere implementeringen af Vare som vi ønsker:

offentlig klasse butik {privat vare vare; offentlig butik (varevare) {this.item = vare; }}

I de næste sektioner vil vi se, hvordan vi kan levere implementeringen af Vare gennem metadata.

Både IoC og DI er enkle begreber, men har dybe implikationer i den måde, vi strukturerer vores systemer på, så de er værd at forstå godt.

4. Spring IoC Container

En IoC-container er et almindeligt kendetegn ved rammer, der implementerer IoC.

I Spring-rammen er IoC-containeren repræsenteret af grænsefladen ApplicationContext. Springcontaineren er ansvarlig for at instantere, konfigurere og samle genstande kendt som bønnersamt styre deres livscyklus.

Forårsrammen giver flere implementeringer af ApplicationContext grænseflade - ClassPathXmlApplicationContext og FileSystemXmlApplicationContext til enkeltstående applikationer, og WebApplicationContext til webapplikationer.

For at samle bønner bruger containeren konfigurationsmetadata, som kan være i form af XML-konfiguration eller annoteringer.

Her er en måde manuelt at starte en container på:

ApplicationContext context = ny ClassPathXmlApplicationContext ("applicationContext.xml");

For at indstille vare attribut i eksemplet ovenfor kan vi bruge metadata. Derefter læser beholderen disse metadata og bruger den til at samle bønner ved kørsel.

Afhængighedsinjektion om foråret kan ske gennem konstruktører, settere eller felter.

5. Konstruktørbaseret afhængighedsinjektion

I tilfælde af konstruktørbaseret afhængighedsinjektion påberåber containeren en konstruktør med argumenter, der hver repræsenterer en afhængighed, som vi vil indstille.

Spring løser hvert argument primært efter type efterfulgt af navnet på attributten og indekset til disambiguation. Lad os se konfigurationen af ​​en bønne og dens afhængigheder ved hjælp af annoteringer:

@Configuration public class AppConfig {@Bean public Item item1 () {return new ItemImpl1 (); } @Bean public Store-butik () {returner ny butik (item1 ()); }}

Det @Konfiguration annotation indikerer, at klassen er en kilde til definitioner af bønner. Vi kan også tilføje det til flere konfigurationsklasser.

Det @Bønne annotering bruges på en metode til at definere en bønne. Hvis vi ikke angiver et brugerdefineret navn, vil bønnenavnet være standardmetodenavnet.

For en bønne med standard singleton omfang, Spring kontrollerer først, om der allerede findes en cachelagret forekomst af bønnen, og opretter kun en ny, hvis den ikke gør det. Hvis vi bruger prototype omfang, containeren returnerer en ny bønneinstans for hvert metodeopkald.

En anden måde at oprette konfigurationen af ​​bønnerne på er ved hjælp af XML-konfiguration:

6. Setterbaseret afhængighedsinjektion

For setterbaseret DI kalder containeren settermetoder i vores klasse efter at have påberåbt sig en ikke-argumentkonstruktør eller ikke-argument statisk fabriksmetode for at instantiere bønnen. Lad os oprette denne konfiguration ved hjælp af annoteringer:

@Bean public Store butik () {Store store = ny butik (); store.setItem (item1 ()); returnere butik; }

Vi kan også bruge XML til den samme konfiguration af bønner:

Konstruktørbaserede og setter-baserede injektionstyper kan kombineres til den samme bønne. Forårsdokumentationen anbefaler brug af konstruktørbaseret injektion til obligatoriske afhængigheder og setterbaseret injektion til valgfri.

7. Markbaseret Afhængighedsinjektion

I tilfælde af feltbaseret DI kan vi indsprøjte afhængighederne ved at markere dem med en @Autowired kommentar:

public class Store {@Autowired private Item item; }

Mens du konstruerer butik objekt, hvis der ikke er nogen konstruktør eller setter metode til at injicere Vare bønne, beholderen bruger refleksion til at injicere Vare ind i butik.

Vi kan også opnå dette ved hjælp af XML-konfiguration.

Denne tilgang ser muligvis enklere og renere ud, men anbefales ikke at bruge, fordi den har nogle ulemper som:

  • Denne metode bruger refleksion til at injicere afhængigheder, hvilket er dyrere end konstruktørbaseret eller setterbaseret injektion
  • Det er virkelig nemt at fortsætte med at tilføje flere afhængigheder ved hjælp af denne tilgang. Hvis du brugte konstruktørinjektion med flere argumenter, ville have fået os til at tro, at klassen gør mere end én ting, der kan krænke princippet om et enkelt ansvar.

Flere oplysninger om @Autowired annotering kan findes i Wiring In Spring-artiklen.

8. Autowiring-afhængigheder

Ledningsføring muliggør, at Spring-containeren automatisk løser afhængigheder mellem samarbejdende bønner ved at inspicere de definerede bønner.

Der er fire tilstande til automatisk ledning af en bønne ved hjælp af en XML-konfiguration:

  • ingen: standardværdien - det betyder, at der ikke bruges autowiring til bønnen, og vi skal udtrykkeligt navngive afhængighederne
  • ved navn: autowiring udføres baseret på ejendommens navn, derfor vil Spring søge efter en bønne med samme navn som den ejendom, der skal indstilles
  • af Type: svarende til ved navn autoledning, kun baseret på ejendomstypen. Dette betyder, at foråret vil se efter en bønne med den samme type ejendom, der skal indstilles. Hvis der er mere end en bønne af den type, kaster rammen en undtagelse.
  • konstruktør: autowiring udføres på baggrund af konstruktørargumenter, hvilket betyder, at Spring vil kigge efter bønner med samme type som konstruktørargumenterne

Lad os for eksempel autotråd punkt 1 bønne defineret ovenfor efter type i butik bønne:

@Bean (autowire = Autowire.BY_TYPE) offentlig klasse Butik {private Item item; public setItem (Item item) {this.item = item; }}

Vi kan også injicere bønner ved hjælp af @Autowired kommentar til autoledning efter type:

public class Store {@Autowired private Item item; }

Hvis der er mere end en bønne af samme type, kan vi bruge @Kvalifikator kommentar til reference til en bønne ved navn:

public class Store {@Autowired @Qualifier ("item1") privat vare vare; }

Lad os nu autowire bønner efter type gennem XML-konfiguration:

Lad os derefter injicere en bønne, der hedder vare ind i vare ejendom af butik bønne ved navn gennem XML:

Vi kan også tilsidesætte autowiring ved at definere afhængigheder eksplicit gennem konstruktørargumenter eller settere.

9. Lazy Initialized Beans

Som standard opretter og konfigurerer containeren alle singletonbønner under initialiseringen. For at undgå dette kan du bruge doven-init attribut med værdi rigtigt på bønnekonfigurationen:

Som en konsekvens af dette punkt 1 bønne initialiseres kun, når den først anmodes om, og ikke ved opstart. Fordelen ved dette er hurtigere initialiseringstid, men kompromisen er, at konfigurationsfejl muligvis kun kan opdages, efter at bønnen er anmodet om, hvilket kan vare flere timer eller endda dage efter, at applikationen allerede har kørt.

10. Konklusion

I denne artikel har vi præsenteret begreberne inversion af kontrol og afhængighedsinjektion og eksemplificeret dem i foråret.

Du kan læse mere om disse begreber i Martin Fowlers artikler:

  • Inversion af kontrolbeholdere og afhængighedsinjektionsmønsteret.
  • Inversion af kontrol

Og du kan lære mere om forårets implementeringer af IoC og DI i Spring Framework Reference Documentation.