Sirkulære avhengigheter om våren

1. Hva er en sirkulær avhengighet?

Det skjer når en bønne A er avhengig av en annen Bean B, og Bean B er også avhengig av Bean A:

Bønne A → Bønne B → Bønne A

Selvfølgelig kan vi ha flere bønner underforstått:

Bean A → Bean B → Bean C → Bean D → Bean E → Bean A

2. Hva skjer om våren

Når vårkonteksten laster alle bønnene, prøver den å lage bønner i den rekkefølgen som er nødvendig for at de skal fungere helt. For eksempel hvis vi ikke hadde en sirkulær avhengighet, som følgende tilfelle:

Bønne A → Bønne B → Bønne C

Våren vil skape bønne C, deretter lage bønne B (og injisere bønne C i den), og deretter lage bønne A (og injisere bønne B i den).

Men når vi har en sirkulær avhengighet, kan ikke Spring bestemme hvilken av bønnene som skal opprettes først, siden de er avhengige av hverandre. I disse tilfellene vil våren øke en BeanCurrentlyInCreationException mens du laster inn kontekst.

Det kan skje om våren når du bruker konstruktørinjeksjon; Hvis du bruker andre typer injeksjoner, bør du ikke finne dette problemet, siden avhengighetene vil bli injisert når de er nødvendige, og ikke på kontekstbelastningen.

3. Et raskt eksempel

La oss definere to bønner som er avhengige av hverandre (via konstruktørinjeksjon):

@Komponent offentlig klasse CircularDependencyA {private CircularDependencyB circB; @Autowired public CircularDependencyA (CircularDependencyB circB) {this.circB = circB; }}
@Komponent offentlig klasse CircularDependencyB {private CircularDependencyA circA; @Autowired public CircularDependencyB (CircularDependencyA circA) {this.circA = circA; }}

Nå kan vi skrive en konfigurasjonsklasse for testene, la oss kalle det TestConfig, som spesifiserer basispakken for å skanne etter komponenter. La oss anta at bønnene våre er definert i pakken "com.baeldung.circulardependency”:

@Configuration @ComponentScan (basePackages = {"com.baeldung.circulardependency"}) offentlig klasse TestConfig {}

Og til slutt kan vi skrive en JUnit-test for å sjekke den sirkulære avhengigheten. Testen kan være tom, siden den sirkulære avhengigheten vil bli oppdaget under kontekstbelastningen.

@RunWith (SpringJUnit4ClassRunner.class) @ContextConfiguration (classes = {TestConfig.class}) public class CircularDependencyTest {@Test public void givenCircularDependency_whenConstructorInjection_thenItFails () {// Tom test; vi vil bare at konteksten skal lastes inn}}

Hvis du prøver å kjøre denne testen, får du følgende unntak:

BeanCurrentlyInCreationException: Feil ved oppretting av bønne med navnet 'circularDependencyA': Forespurt bønne er for øyeblikket under opprettelse: Er det en uløselig sirkulær referanse?

4. Løsningene

Vi vil vise noen av de mest populære måtene å håndtere dette problemet på.

4.1. Omdesign

Når du har en sirkulær avhengighet, er det sannsynlig at du har et designproblem, og ansvaret er ikke godt skilt. Du bør prøve å redesigne komponentene riktig slik at hierarkiet deres er godt utformet og det ikke er behov for sirkulære avhengigheter.

Hvis du ikke kan designe komponentene på nytt (det kan være mange mulige årsaker til det: eldre kode, kode som allerede er testet og ikke kan endres, ikke nok tid eller ressurser til en fullstendig redesign ...), er det noen løsninger du kan prøve.

4.2. Bruk @Lat

En enkel måte å bryte syklusen er å si Spring for å initialisere en av bønnene lat. Det vil si: i stedet for å initialisere bønnen helt, vil den opprette en proxy for å injisere den i den andre bønnen. Den injiserte bønnen blir bare opprettet når den først trengs.

For å prøve dette med koden vår, kan du endre CircularDependencyA til følgende:

@Komponent offentlig klasse CircularDependencyA {private CircularDependencyB circB; @Autowired public CircularDependencyA (@Lazy CircularDependencyB circB) {this.circB = circB; }}

Hvis du kjører testen nå, vil du se at feilen ikke skjer denne gangen.

4.3. Bruk Setter / feltinjeksjon

En av de mest populære løsningene, og også hva vårdokumentasjon foreslår, er å bruke setterinjeksjon.

Enkelt sagt hvis du endrer måtene bønnene dine er koblet til å bruke setterinjeksjon (eller feltinjeksjon) i stedet for konstruktørinjeksjon - det løser problemet. På denne måten skaper Spring bønnene, men avhengighetene injiseres ikke før de er nødvendige.

La oss gjøre det - la oss endre klasser for å bruke setterinjeksjoner og vil legge til et annet felt (beskjed) til Sirkulær avhengighet B. slik at vi kan lage en skikkelig enhetstest:

@Komponent offentlig klasse CircularDependencyA {private CircularDependencyB circB; @Autowired public void setCircB (CircularDependencyB circB) {this.circB = circB; } offentlig CircularDependencyB getCircB () {return circB; }}
@Komponent offentlig klasse CircularDependencyB {private CircularDependencyA circA; private strengmelding = "Hei!"; @Autowired public void setCircA (CircularDependencyA circA) {this.circA = circA; } offentlig String getMessage () {returmelding; }}

Nå må vi gjøre noen endringer i enhetstesten vår:

@RunWith (SpringJUnit4ClassRunner.class) @ContextConfiguration (klasser = {TestConfig.class}) offentlig klasse CircularDependencyTest {@Autowired ApplicationContext context; @Bean public CircularDependencyA getCircularDependencyA () {returner nye CircularDependencyA (); } @Bean public CircularDependencyB getCircularDependencyB () {returner nye CircularDependencyB (); } @Test offentlig ugyldig gittCircularDependency_whenSetterInjection_thenItWorks () {CircularDependencyA circA = context.getBean (CircularDependencyA.class); Assert.assertEquals ("Hei!", CircA.getCircB (). GetMessage ()); }}

Følgende forklarer kommentarene som er sett ovenfor:

@Bønne: For å fortelle vårens rammeverk at disse metodene må brukes for å hente en implementering av bønnene som skal injiseres.

@Test: Testen vil få CircularDependencyA bønne fra sammenhengen og hevde at CircularDependencyB har blitt injisert riktig, og sjekke verdien av dens beskjed eiendom.

4.4. Bruk @PostConstruct

En annen måte å bryte syklusen er å injisere en avhengighet ved hjelp av @Autowired på en av bønnene, og bruk deretter en metode som er merket med @PostConstruct for å sette den andre avhengigheten.

Våre bønner kan ha følgende kode:

@Komponent offentlig klasse CircularDependencyA {@Autowired private CircularDependencyB circB; @ PostConstruct offentlig ugyldig init () {circB.setCircA (dette); } offentlig CircularDependencyB getCircB () {return circB; }}
@Komponent offentlig klasse CircularDependencyB {private CircularDependencyA circA; private strengmelding = "Hei!"; public void setCircA (CircularDependencyA circA) {this.circA = circA; } offentlig String getMessage () {returmelding; }}

Og vi kan kjøre den samme testen vi tidligere hadde, så vi sjekker at unntaket for sirkulær avhengighet fortsatt ikke blir kastet, og at avhengighetene er riktig injisert.

4.5. Implementere ApplicationContextAware og InitialisererBønne

Hvis en av bønnene implementeres ApplicationContextAware, har bønnen tilgang til vårkontekst og kan trekke den andre bønnen ut derfra. Implementering InitialisererBønne vi indikerer at denne bønnen må utføre noen handlinger etter at alle egenskapene er satt; i dette tilfellet ønsker vi å angi avhengighet manuelt.

Koden til bønnene våre vil være:

@Komponent offentlig klasse CircularDependencyA implementerer ApplicationContextAware, InitializingBean {private CircularDependencyB circB; privat ApplicationContext kontekst; offentlig CircularDependencyB getCircB () {return circB; } @ Override public void afterPropertiesSet () kaster unntak {circB = context.getBean (CircularDependencyB.class); } @ Override public void setApplicationContext (final ApplicationContext ctx) kaster BeansException {context = ctx; }}
@Komponent offentlig klasse CircularDependencyB {private CircularDependencyA circA; private strengmelding = "Hei!"; @Autowired public void setCircA (CircularDependencyA circA) {this.circA = circA; } offentlig String getMessage () {returmelding; }}

Igjen kan vi kjøre den forrige testen og se at unntaket ikke kastes og at testen fungerer som forventet.

5. Avslutningsvis

Det er mange måter å håndtere sirkulære avhengigheter på våren. Det første du bør vurdere er å redesigne bønnene dine, slik at det ikke er behov for sirkulære avhengigheter: de er vanligvis et symptom på et design som kan forbedres.

Men hvis du absolutt trenger å ha sirkulære avhengigheter i prosjektet ditt, kan du følge noen av løsningene som er foreslått her.

Den foretrukne metoden er å bruke setterinjeksjoner. Men det er andre alternativer, vanligvis basert på å stoppe Spring fra å styre initialiseringen og injeksjonen av bønnene, og gjøre det selv ved hjelp av en eller annen strategi.

Eksemplene finnes i GitHub-prosjektet.


$config[zx-auto] not found$config[zx-overlay] not found