Körfüggőségek tavasszal

1. Mi a körkörös függőség?

Akkor történik, amikor az A bab egy másik B babtól függ, és a B bab az A babtól is függ:

Bean A → Bean B → Bean A

Természetesen több babot is sejtethetünk:

Bab A → Bab B → Bab C → Bab D → Bab E → Bab A

2. Mi történik tavasszal

Amikor a tavaszi kontextus az összes babot betölti, megpróbálja a babot a teljes működésükhöz szükséges sorrendben létrehozni. Például, ha nem volt körfüggőségünk, például a következő eset:

Bab A → Bab B → Bab C

A tavasz hozza létre a C babot, majd hozza létre a B babot (és injektálja bele a C babot), majd hozza létre az A babot (és adja be B babot).

De amikor körkörös függősége van, Spring nem tudja eldönteni, hogy melyik babot kell először előállítani, mivel egymástól függenek. Ezekben az esetekben Spring felveti a BeanCurrentlyInCreationException miközben betöltjük a kontextust.

Használatkor tavasszal megtörténhet konstruktor befecskendezése; ha más típusú injekciókat használ, akkor nem szabad megtalálni ezt a problémát, mivel a függőségeket akkor kell beadni, amikor szükség van rájuk, és nem a kontextus betöltésére.

3. Gyors példa

Határozzunk meg két babot, amelyek függenek egymástól (konstruktor injekcióval):

@ Component public class CircularDependencyA {private CircularDependencyB circB; @Autowired public CircularDependencyA (CircularDependencyB circB) {this.circB = circB; }}
@Component public class CircularDependencyB {private CircularDependencyA circA; @Autowired public CircularDependencyB (CircularDependencyA circA) {this.circA = circA; }}

Most írhatunk egy Konfigurációs osztályt a tesztekhez, nevezzük TestConfig, amely meghatározza az összetevők keresésére szolgáló alapcsomagot. Tegyük fel, hogy babunk a csomagban van meghatározvacom.baeldung.cirkulardependency”:

@Configuration @ComponentScan (basePackages = {"com.baeldung.circulardependency"}) nyilvános osztály TestConfig {}

Végül írhatunk egy JUnit tesztet a körfüggőség ellenőrzésére. A teszt lehet üres, mivel a körfüggőséget a kontextus betöltése során észlelik.

@RunWith (SpringJUnit4ClassRunner.class) @ContextConfiguration (class = {TestConfig.class}) public class CircularDependencyTest {@Test public void givenCircularDependency_whenConstructorInjection_thenItFails () {// Empty test; csak a kontextust szeretnénk betölteni}}

Ha megpróbálja futtatni ezt a tesztet, a következő kivételt kapja:

BeanCurrentlyInCreationException: Hiba a „circularDependencyA” névvel rendelkező bab létrehozásakor: A kért bab éppen készítés alatt van: Van megoldhatatlan kör alakú hivatkozás?

4. Kerülő megoldások

Megmutatjuk a legnépszerűbb módszereket a probléma kezelésére.

4.1. Újratervezés

Ha körkörös függősége van, akkor valószínűleg tervezési problémája van, és a felelősségek nincsenek jól elkülönítve. Meg kell próbálni az összetevőket megfelelően átalakítani, hogy hierarchiájuk jól megtervezett legyen, és ne legyen szükség körkörös függőségekre.

Ha nem tudja újratervezni az összetevőket (ennek számos oka lehet: régi kód, már tesztelt kód, amelyet nem lehet módosítani, nincs elegendő idő vagy erőforrás a teljes újratervezéshez ...), van néhány próbálkozási lehetőség.

4.2. Használat @Lusta

A ciklus megszakításának egyszerű módja az, hogy Spring lustán inicializálja az egyik babot. Vagyis: a bab teljes inicializálása helyett egy proxy-t hoz létre, hogy a másik babba injektálja. Az injektált bab csak akkor jön létre teljesen, amikor először szükséges.

Ha ezt a kódunkkal szeretné kipróbálni, módosíthatja a CircularDependencyA-t a következőre:

@ Component public class CircularDependencyA {private CircularDependencyB circB; @Autowired public CircularDependencyA (@Lazy CircularDependencyB circB) {this.circB = circB; }}

Ha most futtatja a tesztet, látni fogja, hogy a hiba ezúttal nem történik meg.

4.3. Használja a Szetter / mező injekciót

Az egyik legnépszerűbb megoldás, és amit a Spring dokumentáció is javasol, a szetter injektálás.

Egyszerűen fogalmazva, ha megváltoztatja a babkábelek bekötési módját, hogy szetter-injektálást (vagy terepi injekciót) használjon a konstruktőri injekció helyett - ez megoldja a problémát. Így a tavasz hozza létre a babot, de a függőségeket csak addig injektálják, amíg szükség van rájuk.

Tegyük ezt meg - változtassuk osztályainkat a szetter injekciók használatára, és adjunk hozzá egy másik mezőt (üzenet) nak nek Körkörös függőségB hogy megfelelő egységtesztet hajtsunk végre:

@ Component public class CircularDependencyA {private CircularDependencyB circB; @Autowired public void setCircB (CircularDependencyB circB) {this.circB = circB; } public CircularDependencyB getCircB () {return circB; }}
@ Component public class CircularDependencyB {private CircularDependencyA circA; private String üzenet = "Szia!"; @Autowired public void setCircA (CircularDependencyA circA) {this.circA = circA; } public String getMessage () {return üzenet; }}

Most néhány módosítást kell végrehajtanunk az egység tesztjén:

@RunWith (SpringJUnit4ClassRunner.class) @ContextConfiguration (class = {TestConfig.class}) public class CircularDependencyTest {@Autowired ApplicationContext context; @Bean public CircularDependencyA getCircularDependencyA () {return new CircularDependencyA (); } @Bean public CircularDependencyB getCircularDependencyB () {return new CircularDependencyB (); } @Test public void givenCircularDependency_whenSetterInjection_thenItWorks () {CircularDependencyA circA = context.getBean (CircularDependencyA.class); Assert.assertEquals ("Szia!", CircA.getCircB (). GetMessage ()); }}

Az alábbiakban ismertetjük a fent látható kommentárokat:

@Bab: Annak elmondása a tavaszi keretrendszer számára, hogy ezeket a módszereket kell felhasználni az injektálandó bab megvalósításának visszaszerzéséhez.

@Teszt: A teszt megkapja a CircularDependencyA babot a kontextusból, és azt állítja, hogy a CircularDependencyB-t megfelelően beadták, ellenőrizve annak üzenet ingatlan.

4.4. Használat @PostConstruct

A ciklus megszakításának másik módja a függőség injektálása @Autowired az egyik babon, majd használjon egy metódust @PostConstruct hogy beállítsuk a másik függőséget.

A babunknak a következő kódja lehet:

@Component public class CircularDependencyA {@Autowired private CircularDependencyB circB; @PostConstruct public void init () {circB.setCircA (this); } public CircularDependencyB getCircB () {return circB; }}
@ Component public class CircularDependencyB {private CircularDependencyA circA; privát karakterlánc üzenet = "Sziasztok!"; public void setCircA (CircularDependencyA circA) {this.circA = circA; } public String getMessage () {return üzenet; }}

És lefuttathatjuk ugyanazt a tesztet, amely korábban volt, ezért ellenőrizzük, hogy a körfüggőségi kivételt továbbra sem dobják-e meg, és hogy a függőségeket megfelelően injektálják-e.

4.5. Végrehajtás ApplicationContextAware és InitializingBean

Ha az egyik bab végrehajtja ApplicationContextAware, a bab hozzáfér a tavaszi kontextushoz, és onnan kivonhatja a másik babot. Végrehajtás InitializingBean jelezzük, hogy ennek a babnak az összes tulajdonságának beállítása után bizonyos műveleteket kell végrehajtania; ebben az esetben manuálisan szeretnénk beállítani a függőségünket.

A babunk kódja a következő lenne:

@Component public class CircularDependencyA implementálja az ApplicationContextAware, InitializingBean {private CircularDependencyB circB; privát ApplicationContext kontextus; public CircularDependencyB getCircB () {return circB; } @Orride public void afterPropertiesSet () dobja a Kivételt {circB = context.getBean (CircularDependencyB.class); } @Orride public void setApplicationContext (final ApplicationContext ctx) dobja a BeansException {context = ctx; }}
@Component public class CircularDependencyB {private CircularDependencyA circA; privát karakterlánc üzenet = "Szia!"; @Autowired public void setCircA (CircularDependencyA circA) {this.circA = circA; } public String getMessage () {return üzenet; }}

Ismét lefuttathatjuk az előző tesztet, és láthatjuk, hogy a kivétel nincs dobva, és hogy a teszt a várt módon működik.

5. Összegzésként

Tavasszal sokféle módon lehet kezelni a körkörös függőségeket. Az első dolog, amit figyelembe kell venni, a bab babának újratervezése, így nincs szükség körkörös függőségekre: ezek általában egy olyan fejlesztési tünet, amely javítható.

De ha feltétlenül körkörös függőségekkel kell rendelkeznie a projektben, akkor kövesse az itt javasolt megoldásokat.

Az előnyben részesített módszer a szetter injekciók alkalmazása. De vannak más alternatívák is, amelyek általában azon alapulnak, hogy megakadályozzuk Springet a bab inicializálásának és injektálásának kezelésében, és ezt te magad csinálod egyik vagy másik stratégia segítségével.

A példák a GitHub projektben találhatók.