Obehové závislosti na jar

1. Čo je to kruhová závislosť?

Stáva sa to, keď fazuľa A závisí od inej fazule B a fazuľa B závisí aj od fazule A:

Bean A → Bean B → Bean A

Samozrejme by sme mohli mať naznačených viac fazúľ:

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

2. Čo sa deje na jar

Keď jarný kontext načítava všetky fazule, pokúsi sa vytvoriť fazuľu v poradí potrebnom na to, aby fungovala úplne. Napríklad, ak sme nemali kruhovú závislosť, ako napríklad nasledujúci prípad:

Fazuľa A → Fazuľa B → Fazuľa C.

Jar vytvorí fazuľu C, potom vytvorí fazuľu B (a vstrekne do nej fazuľu C), potom vytvorí fazuľu A (a vstrekne do nej fazuľu B).

Ale keď má kruhová závislosť, Spring nemôže rozhodnúť, ktorá z fazúľ by sa mala vytvoriť ako prvá, pretože závisia jeden na druhom. V týchto prípadoch jar zvýši a BeanCurrentlyInCreationException pri načítaní kontextu.

Môže sa to stať na jar pri používaní injektor konštruktora; ak používate iné typy injekcií, nemali by ste tento problém nájsť, pretože závislosti sa vstreknú, keď sú potrebné, a nie pri načítaní kontextu.

3. Rýchly príklad

Definujme dve fazule, ktoré závisia jedna na druhej (pomocou vstrekovania konštruktora):

@ Komponenta verejná trieda CircularDependencyA {private CircularDependencyB circB; @Autowired public CircularDependencyA (CircularDependencyB circB) {this.circB = circB; }}
@ Komponenta verejná trieda CircularDependencyB {private CircularDependencyA circA; @Autowired public CircularDependencyB (CircularDependencyA circA) {this.circA = circA; }}

Teraz môžeme pre testy napísať konfiguračnú triedu, nazvime to TestConfig, ktorý určuje základný balík na skenovanie komponentov. Predpokladajme, že naše zrná sú definované v balíku „com.baeldung.circulardependency”:

@Configuration @ComponentScan (basePackages = {"com.baeldung.circulardependency"}) verejná trieda TestConfig {}

A nakoniec môžeme napísať test JUnit na kontrolu kruhovej závislosti. Test môže byť prázdny, pretože kruhová závislosť bude zistená počas načítania kontextu.

@RunWith (SpringJUnit4ClassRunner.class) @ContextConfiguration (classes = {TestConfig.class}) verejná trieda CircularDependencyTest {@Test public void givenCircularDependency_whenConstructorInjection_thenItFails () {// Prázdny test; chceme iba načítať kontext}}

Ak sa pokúsite spustiť tento test, zobrazí sa nasledujúca výnimka:

BeanCurrentlyInCreationException: Chyba pri vytváraní fazule s názvom 'circleDependencyA': Požadovaná fazuľa sa momentálne vytvára: Existuje nevyriešiteľný kruhový odkaz?

4. Riešenia

Ukážeme niektoré z najpopulárnejších spôsobov riešenia tohto problému.

4.1. Redizajn

Ak máte kruhovú závislosť, je pravdepodobné, že máte problém s dizajnom a zodpovednosti nie sú dobre oddelené. Mali by ste sa pokúsiť správne redizajnovať komponenty, aby bola ich hierarchia dobre navrhnutá a nie sú potrebné kruhové závislosti.

Ak nemôžete prepracovať komponenty (môže to mať veľa možných dôvodov: starší kód, kód, ktorý už bol testovaný a nemožno ho upraviť, nie je dostatok času alebo zdrojov na úplné prepracovanie ...), je treba vyskúšať niekoľko riešení.

4.2. Použite @Lazy

Jednoduchý spôsob, ako prerušiť cyklus, je povedať, že Jar lenivo inicializuje jednu z fazúľ. To znamená: namiesto úplnej inicializácie fazule vytvorí proxy, ktorý ju vloží do druhej fazule. Injekovaná fazuľa bude úplne vytvorená, až keď to bude potrebné.

Ak to chcete skúsiť s našim kódom, môžete zmeniť CircularDependencyA na toto:

@ Komponenta verejná trieda CircularDependencyA {private CircularDependencyB circB; @Autowired public CircularDependencyA (@Lazy CircularDependencyB circB) {this.circB = circB; }}

Ak test spustíte teraz, uvidíte, že chyba sa tentokrát nestane.

4.3. Použite vstrekovač / vstrekovanie poľa

Jedným z najpopulárnejších riešení a tiež toho, čo navrhuje jarná dokumentácia, je použitie vstrekovania setra.

Jednoducho povedané, ak zmeníte spôsob, akým je vaše fazuľa zapojená, aby používala vstrekovanie setra (alebo poľné vstrekovanie) namiesto injektovania konštruktorom - problém sa tým vyrieši. Týmto spôsobom jar vytvára fazuľa, ale závislosti sa vstrekujú až potom, keď sú potrebné.

Urobme to - zmeňme naše triedy tak, aby používali vstrekovacie injekcie, a pridáme ďalšie pole (správa) až CircularDependencyB aby sme mohli vykonať správny test jednotky:

@ Komponenta verejná trieda CircularDependencyA {private CircularDependencyB circB; @Autowired public void setCircB (CircularDependencyB circB) {this.circB = circB; } public CircularDependencyB getCircB () {návrat cirB; }}
@ Komponenta verejná trieda CircularDependencyB {private CircularDependencyA circA; private String message = "Ahoj!"; @Autowired public void setCircA (CircularDependencyA circA) {this.circA = circA; } public String getMessage () {spätna sprava; }}

Teraz musíme urobiť niekoľko zmien v našom teste jednotiek:

@RunWith (SpringJUnit4ClassRunner.class) @ContextConfiguration (triedy = {TestConfig.class}) verejná trieda CircularDependencyTest {@Autowired ApplicationContext kontext; @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 ("Ahoj!", CircA.getCircB (). GetMessage ()); }}

Nasledujúci text vysvetľuje vyššie uvedené anotácie:

@Bean: Povedať jarnému rámcu, že tieto metódy sa musia použiť na získanie implementácie fazule, ktorá sa má vstreknúť.

@Test: Test získa z kontextu fazuľa CircularDependencyA a potvrdí, že jej CircularDependencyB bol vložený správne, pričom skontroluje hodnotu jej správa nehnuteľnosť.

4.4. Použite @PostConstruct

Ďalším spôsobom, ako prerušiť cyklus, je vloženie závislosti pomocou @Autowired na jednej z fazúľ a potom použite metódu s poznámkami @PostConstruct nastaviť druhú závislosť.

Naše fazule môžu mať nasledujúci kód:

@ Komponenta verejná trieda CircularDependencyA {@Autowired private CircularDependencyB circB; @PostConstruct public void init () {circB.setCircA (this); } public CircularDependencyB getCircB () {návrat cirB; }}
@ Komponenta verejná trieda CircularDependencyB {private CircularDependencyA circA; private String message = "Ahoj!"; public void setCircA (CircularDependencyA circA) {this.circA = circA; } public String getMessage () {spätna sprava; }}

A môžeme spustiť rovnaký test, aký sme mali predtým, takže skontrolujeme, či sa stále nevyhadzuje výnimka z kruhovej závislosti a či sú závislosti správne vložené.

4.5. Implementovať ApplicationContextAware a InitializingBean

Ak sa jedna z fazule realizuje ApplicationContextAware, fazuľa má prístup k jarnému kontextu a môže odtiaľ extrahovať druhú fazuľu. Implementácia InitializingBean naznačujeme, že táto fazuľa musí po vykonaní všetkých svojich vlastností urobiť nejaké akcie; v takom prípade chceme našu závislosť nastaviť manuálne.

Kód našich fazúľ by bol:

@ Komponenta verejná trieda CircularDependencyA implementuje ApplicationContextAware, InitializingBean {private CircularDependencyB circB; súkromný kontext ApplicationContext; public CircularDependencyB getCircB () {return cirB; } @Override public void afterPropertiesSet () vyvolá výnimku {circB = context.getBean (CircularDependencyB.class); } @Override public void setApplicationContext (final ApplicationContext ctx) vyvolá BeansException {context = ctx; }}
@ Komponenta verejná trieda CircularDependencyB {private CircularDependencyA circA; private String message = "Ahoj!"; @Autowired public void setCircA (CircularDependencyA circA) {this.circA = circA; } public String getMessage () {spätna sprava; }}

Opäť môžeme spustiť predchádzajúci test a zistiť, že výnimka nie je vyvolaná a že test funguje podľa očakávania.

5. Na záver

Na jar existuje veľa spôsobov, ako riešiť kruhové závislosti. Prvá vec, ktorú je potrebné zvážiť, je prepracovať fazuľa, takže nie sú potrebné kruhové závislosti: sú zvyčajne príznakom dizajnu, ktorý je možné vylepšiť.

Ale ak vo svojom projekte nevyhnutne potrebujete kruhové závislosti, môžete postupovať podľa niektorých riešení, ktoré sú tu navrhnuté.

Preferovanou metódou je použitie injekčných liekoviek. Existujú ale aj iné alternatívy, ktoré sú zvyčajne založené na tom, že zabráni spoločnosti Spring riadiť inicializáciu a vstrekovanie fazule, a urobí to sám pomocou jednej alebo druhej stratégie.

Príklady nájdete v projekte GitHub.


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