Introduksjon til inversjon av kontroll og avhengighetsinjeksjon med våren

1. Oversikt

I denne artikkelen vil vi introdusere konseptene IoC (Inversion of Control) og DI (Dependency Injection), og vi tar en titt på hvordan disse implementeres i vårrammen.

2. Hva er inversjon av kontroll?

Inversjon av kontroll er et prinsipp innen programvareteknikk der kontrollen av objekter eller deler av et program overføres til en container eller ramme. Det brukes oftest i sammenheng med objektorientert programmering.

I motsetning til tradisjonell programmering, der vår tilpassede kode ringer til et bibliotek, gjør IoC et rammeverk for å ta kontroll over flyten til et program og ringe til vår tilpassede kode. For å muliggjøre dette bruker rammeverk abstraksjoner med ekstra atferd innebygd. Hvis vi vil legge til vår egen atferd, må vi utvide klassene i rammeverket eller plugin våre egne klasser.

Fordelene med denne arkitekturen er:

  • frakoble utførelsen av en oppgave fra implementeringen
  • gjør det lettere å bytte mellom forskjellige implementeringer
  • større modularitet til et program
  • større letthet i å teste et program ved å isolere en komponent eller spotte dens avhengigheter og la komponenter kommunisere gjennom kontrakter

Inversjon av kontroll kan oppnås gjennom forskjellige mekanismer, slik som: Strategidesignmønster, Service Locator-mønster, Fabrikkmønster og Dependency Injection (DI).

Vi skal se på DI neste.

3. Hva er avhengighetsinjeksjon?

Avhengighetsinjeksjon er et mønster for implementering av IoC, der kontrollen som inverteres er innstillingen av objektets avhengighet.

Handlingen med å koble gjenstander med andre gjenstander, eller "injisere" gjenstander i andre gjenstander, gjøres av en samler i stedet for av selve gjenstandene.

Slik oppretter du en objektavhengighet i tradisjonell programmering:

offentlig klasse Butikk {privat vare; offentlig butikk () {item = new ItemImpl1 (); }}

I eksemplet ovenfor må vi starte en implementering av Punkt grensesnitt i butikk klassen selv.

Ved å bruke DI kan vi skrive om eksemplet uten å spesifisere implementeringen av Punkt som vi ønsker:

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

I de neste avsnittene vil vi se hvordan vi kan tilby implementeringen av Punkt gjennom metadata.

Både IoC og DI er enkle konsepter, men har dype implikasjoner i måten vi strukturerer systemene våre på, så de er vel verdt å forstå godt.

4. Vårens IoC-beholder

En IoC-container er et vanlig kjennetegn på rammer som implementerer IoC.

I vår-rammeverket er IoC-containeren representert av grensesnittet ApplicationContext. Spring Container er ansvarlig for å sette i gang, konfigurere og montere gjenstander kjent som bønner, samt administrere livssyklusen.

Vårrammen gir flere implementeringer av ApplicationContext grensesnitt - ClassPathXmlApplicationContext og FileSystemXmlApplicationContext for frittstående applikasjoner, og WebApplicationContext for webapplikasjoner.

For å samle bønner, bruker beholderen konfigurasjonsmetadata, som kan være i form av XML-konfigurasjon eller merknader.

Her er en måte å manuelt starte en container på:

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

For å stille inn punkt attributtet i eksemplet ovenfor, kan vi bruke metadata. Deretter vil beholderen lese denne metadata og bruke den til å montere bønner ved kjøretid.

Avhengighetsinjeksjon om våren kan gjøres gjennom konstruktører, settere eller felt.

5. Konstruktørbasert avhengighetsinjeksjon

I tilfelle av konstruktørbasert avhengighetsinjeksjon, vil beholderen påkalle en konstruktør med argumenter som hver representerer en avhengighet vi vil sette.

Våren løser hvert argument primært etter type, etterfulgt av navnet på attributtet og indeksen for tvetydighet. La oss se konfigurasjonen av en bønne og dens avhengigheter ved hjelp av merknader:

@Configuration public class AppConfig {@Bean public Item item1 () {return new ItemImpl1 (); } @Bean offentlig butikk () {returner ny butikk (item1 ()); }}

De @Konfigurasjon kommentar indikerer at klassen er en kilde til definisjoner av bønner. Vi kan også legge det til flere konfigurasjonsklasser.

De @Bønne kommentar brukes på en metode for å definere en bønne. Hvis vi ikke spesifiserer et egendefinert navn, vil bønnenavnet som standard være metodenavnet.

For en bønne med standard singleton omfang, Spring sjekker først om en bufret forekomst av bønnen allerede eksisterer, og oppretter bare en ny hvis den ikke gjør det. Hvis vi bruker prototype omfang returnerer beholderen en ny bønneinstans for hver metodeanrop.

En annen måte å lage konfigurasjonen av bønnene på er gjennom XML-konfigurasjon:

6. Setterbasert avhengighetsinjeksjon

For setterbasert DI vil containeren kalle settermetoder i vår klasse, etter å ha påkalt en ikke-argumentkonstruktør eller ikke-argument statisk fabrikkmetode for å instantiere bønnen. La oss lage denne konfigurasjonen ved hjelp av merknader:

@Bean offentlig butikk () {butikk = ny butikk (); store.setItem (item1 ()); retur butikk; }

Vi kan også bruke XML for samme konfigurasjon av bønner:

Konstruktørbaserte og setterbaserte injeksjonstyper kan kombineres for samme bønne. Vårdokumentasjonen anbefaler bruk av konstruktørbasert injeksjon for obligatoriske avhengigheter, og setterbasert injeksjon for valgfrie.

7. Feltbasert Avhengighetsinjeksjon

I tilfelle feltbasert DI kan vi injisere avhengighetene ved å merke dem med en @Autowired kommentar:

public class Store {@Autowired private Item item; }

Mens du konstruerer butikk objekt, hvis det ikke er noen konstruktør eller settermetode for å injisere Punkt bønne, vil beholderen bruke refleksjon for å injisere Punkt inn i butikk.

Vi kan også oppnå dette ved hjelp av XML-konfigurasjon.

Denne tilnærmingen kan se enklere og renere ut, men anbefales ikke å bruke fordi den har noen ulemper som:

  • Denne metoden bruker refleksjon for å injisere avhengighetene, noe som er dyrere enn konstruktørbasert eller setterbasert injeksjon
  • Det er veldig enkelt å fortsette å legge til flere avhengigheter ved hjelp av denne tilnærmingen. Hvis du brukte konstruktørinjeksjon med flere argumenter, ville det fått oss til å tro at klassen gjør mer enn én ting som kan bryte med prinsippet om enkelt ansvar.

Mer informasjon om @Autowired merknader kan du finne i Wiring In Spring artikkel.

8. Autowiring avhengigheter

Kabling tillater Spring Container å automatisk løse avhengigheter mellom samarbeidende bønner ved å inspisere bønner som er definert.

Det er fire moduser for automatisk ledning av en bønne ved hjelp av en XML-konfigurasjon:

  • Nei: standardverdien - dette betyr at ingen autowiring brukes til bønnen, og vi må eksplisitt navngi avhengighetene
  • ved navn: Autoledning gjøres ut fra navnet på eiendommen, derfor vil Spring se etter en bønne med samme navn som eiendommen som må settes
  • byType: ligner på ved navn automatisk ledning, bare basert på typen eiendom. Dette betyr at våren vil se etter en bønne med samme type eiendom å sette. Hvis det er mer enn en bønne av den typen, kaster rammeverket et unntak.
  • konstruktør: autowiring gjøres basert på konstruktørargumenter, noe som betyr at våren vil se etter bønner med samme type som konstruktørargumentene

La oss for eksempel autoledre element1 bønne definert ovenfor etter type inn i butikk bønne:

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

Vi kan også injisere bønner ved hjelp av @Autowired kommentar for autokobling etter type:

public class Store {@Autowired private Item item; }

Hvis det er mer enn en bønne av samme type, kan vi bruke @Kvalifiserende kommentar for å referere til en bønne ved navn:

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

La oss nå autowire bønner etter type gjennom XML-konfigurasjon:

La oss deretter injisere en bønne som heter punkt inn i det punkt tilhører butikk bønne etter navn gjennom XML:

Vi kan også overstyre autowiring ved å definere avhengigheter eksplisitt gjennom konstruktørargumenter eller settere.

9. Lazy Initialized Beans

Som standard oppretter og konfigurerer beholderen alle singletonbønner under initialiseringen. For å unngå dette kan du bruke lat-init attributt med verdi ekte på bønnekonfigurasjonen:

Som en konsekvens av dette element1 bønne vil først initialiseres når den først blir bedt om det, og ikke ved oppstart. Fordelen med dette er raskere initialiseringstid, men kompromissen er at konfigurasjonsfeil kan oppdages først etter at bønnen er forespurt, noe som kan ta flere timer eller dager etter at applikasjonen allerede har kjørt.

10. Konklusjon

I denne artikkelen har vi presentert begrepene inversjon av kontroll og avhengighetsinjeksjon og eksemplifisert dem i vårrammen.

Du kan lese mer om disse konseptene i Martin Fowlers artikler:

  • Inversjon av kontrollbeholdere og avhengighetsinjeksjonsmønsteret.
  • Inversjon av kontroll

Og du kan lære mer om vårimplementeringene av IoC og DI i Spring Framework Reference Documentation.