Cirkulære afhængigheder om foråret

1. Hvad er en cirkulær afhængighed?

Det sker, når en bønne A afhænger af en anden bønne B, og bønnen B også afhænger af bønnen A:

Bønner A → Bønner B → Bønner A

Naturligvis kunne vi have flere bønner underforstået:

Bønner A → Bønner B → Bønner C → Bønner D → Bønner E → Bønner A

2. Hvad sker der om foråret

Når Spring-kontekst indlæser alle bønner, forsøger den at oprette bønner i den rækkefølge, der er nødvendig for, at de kan arbejde fuldstændigt. For eksempel, hvis vi ikke havde en cirkulær afhængighed, som i følgende tilfælde:

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

Foråret vil skabe bønne C, derefter oprette bønne B (og injicere bønne C i den), derefter oprette bønne A (og injicere bønne B i den).

Men når vi har en cirkulær afhængighed, kan Spring ikke beslutte, hvilken af ​​bønnerne der skal oprettes først, da de er afhængige af hinanden. I disse tilfælde vil Spring hæve en BeanCurrentlyInCreationException under indlæsning af kontekst.

Det kan ske om foråret, når du bruger det konstruktørinjektion; hvis du bruger andre typer injektioner, bør du ikke finde dette problem, da afhængighederne injiceres, når de er nødvendige, og ikke under kontekstindlæsningen.

3. Et hurtigt eksempel

Lad os definere to bønner, der er afhængige af hinanden (via konstruktionsinjektion):

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

Nu kan vi skrive en konfigurationsklasse til testene, lad os kalde det TestConfig, der specificerer basispakken, der skal scannes efter komponenter. Lad os antage, at vores bønner er defineret i pakke "com.baeldung.circulardependency”:

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

Og endelig kan vi skrive en JUnit-test for at kontrollere den cirkulære afhængighed. Testen kan være tom, da den cirkulære afhængighed registreres under kontekstindlæsningen.

@RunWith (SpringJUnit4ClassRunner.class) @ContextConfiguration (classes = {TestConfig.class}) public class CircularDependencyTest {@Test public void givenCircularDependency_whenConstructorInjection_thenItFails () {// Tom test; vi vil bare have, at konteksten indlæses}}

Hvis du forsøger at køre denne test, får du følgende undtagelse:

BeanCurrentlyInCreationException: Fejl ved oprettelse af bønner med navnet 'circularDependencyA': Den anmodede bønne er i øjeblikket under oprettelse: Er der en uopløselig cirkulær reference?

4. Løsningerne

Vi viser nogle af de mest populære måder at håndtere dette problem på.

4.1. Redesign

Når du har en cirkulær afhængighed, er det sandsynligt, at du har et designproblem, og ansvaret er ikke godt adskilt. Du bør prøve at redesigne komponenterne korrekt, så deres hierarki er godt designet, og der er ikke behov for cirkulære afhængigheder.

Hvis du ikke kan redesigne komponenterne (der kan være mange mulige årsager til det: ældre kode, kode, der allerede er testet og ikke kan ændres, ikke nok tid eller ressourcer til en komplet redesign ...), er der nogle løsninger at prøve.

4.2. Brug @Doven

En enkel måde at bryde cyklussen på er at sige Spring for at initialisere en af ​​bønnerne doven. Det vil sige: i stedet for at initialisere bønnen fuldt ud, opretter den en proxy til at injicere den i den anden bønne. Den injicerede bønne oprettes kun fuldt ud, når den først er nødvendig.

For at prøve dette med vores kode kan du ændre CircularDependencyA til følgende:

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

Hvis du kører testen nu, vil du se, at fejlen ikke sker denne gang.

4.3. Brug Setter / feltinjektion

En af de mest populære løsninger, og også hvad Spring-dokumentation foreslår, er at bruge setterinjektion.

Kort sagt, hvis du ændrer måden, hvorpå dine bønner er kablet til at bruge setterinjektion (eller markinjektion) i stedet for konstruktionsinjektion - det løser problemet. På denne måde skaber Spring bønnerne, men afhængighederne injiceres ikke, før de er nødvendige.

Lad os gøre det - lad os ændre vores klasser til at bruge setterinjektioner og tilføje et andet felt (besked) til Cirkulær afhængighed B. så vi kan lave en ordentlig enhedstest:

@Komponent offentlig klasse CircularDependencyA {privat CircularDependencyB circB; @Autowired public void setCircB (CircularDependencyB circB) {this.circB = circB; } offentlig CircularDependencyB getCircB () {return circB; }}
@Komponent offentlig klasse CircularDependencyB {private CircularDependencyA circA; privat streng besked = "Hej!"; @Autowired public void setCircA (CircularDependencyA circA) {this.circA = circA; } public String getMessage () {return message; }}

Nu er vi nødt til at foretage nogle ændringer i vores enhedstest:

@RunWith (SpringJUnit4ClassRunner.class) @ContextConfiguration (classes = {TestConfig.class}) public class CircularDependencyTest {@Autowired ApplicationContext context; @Bean public CircularDependencyA getCircularDependencyA () {returner ny CircularDependencyA (); } @Bean public CircularDependencyB getCircularDependencyB () {returner ny CircularDependencyB (); } @ Test offentligt ugyldigt givetCircularDependency_whenSetterInjection_thenItWorks () {CircularDependencyA circA = context.getBean (CircularDependencyA.class); Assert.assertEquals ("Hej!", CircA.getCircB (). GetMessage ()); }}

Følgende forklarer kommentarerne set ovenfor:

@Bønne: At fortælle Spring framework, at disse metoder skal bruges til at hente en implementering af de bønner, der skal injiceres.

@Prøve: Testen får CircularDependencyA bønne fra sammenhængen og hævder, at dens CircularDependencyB er blevet injiceret korrekt og kontrollerer værdien af ​​dens besked ejendom.

4.4. Brug @PostConstruct

En anden måde at bryde cyklussen på er at injicere en afhængighed ved hjælp af @Autowired på en af ​​bønnerne, og brug derefter en metode, der er kommenteret med @PostConstruct for at indstille den anden afhængighed.

Vores bønner kunne have følgende kode:

@Komponent offentlig klasse CircularDependencyA {@Autowired privat CircularDependencyB circB; @PostConstruct offentlig ugyldig init () {circB.setCircA (dette); } offentlig CircularDependencyB getCircB () {return circB; }}
@Komponent offentlig klasse CircularDependencyB {private CircularDependencyA circA; privat streng besked = "Hej!"; public void setCircA (CircularDependencyA circA) {this.circA = circA; } public String getMessage () {return message; }}

Og vi kan køre den samme test, som vi tidligere havde, så vi kontrollerer, at den cirkulære afhængighedsundtagelse stadig ikke kastes, og at afhængighederne injiceres korrekt.

4.5. Implementere ApplicationContextAware og InitialisererBønne

Hvis en af ​​bønnerne implementeres ApplicationContextAware, bønnen har adgang til Spring-sammenhæng og kan udtrække den anden bønne derfra. Implementerer InitialisererBønne vi indikerer, at denne bønne skal udføre nogle handlinger, efter at alle dens egenskaber er indstillet; i dette tilfælde vil vi manuelt indstille vores afhængighed.

Koden for vores bønner ville være:

@Komponent offentlig klasse CircularDependencyA implementerer ApplicationContextAware, InitializingBean {private CircularDependencyB circB; privat ApplicationContext kontekst; public CircularDependencyB getCircB () {return circB; } @ Override offentlig ugyldighed efterPropertiesSet () kaster undtagelse {circB = context.getBean (CircularDependencyB.class); } @ Override public void setApplicationContext (final ApplicationContext ctx) kaster BeansException {context = ctx; }}
@Komponent offentlig klasse CircularDependencyB {private CircularDependencyA circA; privat streng besked = "Hej!"; @Autowired public void setCircA (CircularDependencyA circA) {this.circA = circA; } public String getMessage () {return message; }}

Igen kan vi køre den forrige test og se, at undtagelsen ikke kastes, og at testen fungerer som forventet.

5. Afslutningsvis

Der er mange måder at håndtere cirkulære afhængigheder om foråret. Den første ting at overveje er at redesigne dine bønner, så der ikke er behov for cirkulære afhængigheder: de er normalt et symptom på et design, der kan forbedres.

Men hvis du absolut har brug for cirkulære afhængigheder i dit projekt, kan du følge nogle af de løsninger, der er foreslået her.

Den foretrukne metode er at anvende setterinjektioner. Men der er andre alternativer, generelt baseret på at stoppe Spring fra at styre initialiseringen og injektionen af ​​bønnerne og gøre det selv ved hjælp af en eller anden strategi.

Eksemplerne findes i GitHub-projektet.


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