Kružne ovisnosti u proljeće

1. Što je kružna ovisnost?

To se događa kada grah A ovisi o drugom grahu B, a grah B ovisi i o grahu A:

Grah A → Grah B → Grah A

Naravno, mogli bismo podrazumijevati više graha:

Grah A → Grah B → Grah C → Grah D → Grah E → Grah A

2. Što se događa u proljeće

Kada kontekst Spring učitava sve grah, on pokušava stvoriti grah redoslijedom potrebnim za njihov potpuni rad. Na primjer, ako nismo imali kružnu ovisnost, kao u sljedećem slučaju:

Grah A → Grah B → Grah C

Proljeće će stvoriti zrno C, zatim stvoriti zrno B (i u njega ubrizgati zrno C), zatim stvoriti zrno A (i u njega ubrizgati zrno B).

Ali, kad ima kružnu ovisnost, Spring ne može odlučiti koji od graha treba stvoriti prvi, jer ovise jedan o drugome. U tim će slučajevima Proljeće podići a BeanCurrentlyInCreationException tijekom učitavanja konteksta.

To se može dogoditi u proljeće kada se koristi ubrizgavanje konstruktora; ako koristite druge vrste injekcija, ne biste trebali pronaći ovaj problem, jer će se ovisnosti ubrizgati kada su potrebne, a ne na kontekstualnom učitavanju.

3. Brzi primjer

Definirajmo dva zrna koja međusobno ovise (putem ubrizgavanja konstruktora):

@Component javna klasa CircularDependencyA {private CircularDependencyB circB; @Autowired javni CircularDependencyA (CircularDependencyB circB) {this.circB = circB; }}
@Component javna klasa CircularDependencyB {private CircularDependencyA circA; @Autowired javni CircularDependencyB (CircularDependencyA circA) {this.circA = circA; }}

Sada možemo napisati klasu konfiguracije za testove, nazovimo je TestConfig, koji navodi osnovni paket za skeniranje komponenata. Pretpostavimo da je naš grah definiran u paketu "com.baeldung.kružna ovisnost”:

@Configuration @ComponentScan (basePackages = {"com.baeldung.circulardependency"}) javna klasa TestConfig {}

I na kraju možemo napisati JUnit test za provjeru kružne ovisnosti. Test može biti prazan, jer će se kružna ovisnost otkriti tijekom kontekstnog učitavanja.

@RunWith (SpringJUnit4ClassRunner.class) @ContextConfiguration (classes = {TestConfig.class}) public class CircularDependencyTest {@Test public void givenCircularDependency_whenConstructorInjection_thenItFails () {// Prazan test; samo želimo da se kontekst učita}}

Ako pokušate pokrenuti ovaj test, dobit ćete sljedeću iznimku:

BeanCurrentlyInCreationException: Pogreška pri stvaranju graha s imenom 'circularDependencyA': Traženi grah je trenutno u izradi: Postoji li nerješiva ​​kružna referenca?

4. Zaobilazna rješenja

Pokazat ćemo neke od najpopularnijih načina rješavanja ovog problema.

4.1. Redizajn

Kada imate kružnu ovisnost, vjerojatno imate problema s dizajnom i odgovornosti nisu dobro razdvojene. Trebali biste pokušati pravilno redizajnirati komponente tako da je njihova hijerarhija dobro dizajnirana i nema potrebe za kružnim ovisnostima.

Ako ne možete redizajnirati komponente (za to može postojati mnogo mogućih razloga: naslijeđeni kôd, kôd koji je već testiran i koji se ne može izmijeniti, nema dovoljno vremena ili resursa za cjeloviti redizajn ...), pokušati ćete zaobići nekoliko rješenja.

4.2. Koristiti @Lijen

Jednostavan način da prekinete ciklus je izgovor da Proljeće lijeno inicijalizira jedan od graha. Odnosno: umjesto potpune inicijalizacije graha, stvorit će proxy za ubrizgavanje u drugi grah. Ubrizgani grah u potpunosti će se stvoriti tek kad je to prvo potrebno.

Da biste isprobali ovo s našim kodom, možete promijeniti CircularDependencyA u sljedeće:

@Component javna klasa CircularDependencyA {private CircularDependencyB circB; @Autowired javni CircularDependencyA (@Lazy CircularDependencyB circB) {this.circB = circB; }}

Ako sada pokrenete test, vidjet ćete da se pogreška ovaj put ne događa.

4.3. Koristite Setter / Field Injection

Jedno od najpopularnijih rješenja, a također i ono što proljetna dokumentacija predlaže, je upotreba ubrizgavanja setera.

Jednostavno rečeno, ako promijenite načine povezivanja graha da biste koristili ubrizgavanje setera (ili ubrizgavanje polja) umjesto ubrizgavanja konstruktora - to rješava problem. Na ovaj način Spring stvara grah, ali ovisnosti se ne ubrizgavaju sve dok nisu potrebne.

Učinimo to - promijenimo klase kako bi koristili injekcije postavljača i dodati ćemo drugo polje (poruka) do CircularDependencyB tako da možemo napraviti odgovarajući jedinični test:

@Component javna klasa CircularDependencyA {private CircularDependencyB circB; @Autowired javna praznina setCircB (CircularDependencyB circB) {this.circB = circB; } public CircularDependencyB getCircB () {return circB; }}
@Component javna klasa CircularDependencyB {private CircularDependencyA circA; privatna niska poruka = ​​"Bok!"; @Autowired javna praznina setCircA (CircularDependencyA circA) {this.circA = circA; } javni String getMessage () {return poruka; }}

Sada moramo unijeti neke promjene u naš jedinični test:

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

Sljedeće objašnjava gore navedene bilješke:

@Grah: Da kažem Spring framework-u da se ove metode moraju koristiti za dohvaćanje implementacije graha za ubrizgavanje.

@Test: Test će dobiti CircularDependencyA grah iz konteksta i ustvrditi da je njegova CircularDependencyB pravilno ubrizgana, provjeravajući vrijednost njegovog poruka imovine.

4.4. Koristiti @PostConstruct

Drugi način prekida ciklusa je ubrizgavanje ovisnosti pomoću @Autowired na jednom od zrna, a zatim upotrijebite metodu označenu s @PostConstruct postaviti drugu ovisnost.

Naš grah mogao bi imati sljedeći kod:

@Component javna klasa CircularDependencyA {@Autowired private CircularDependencyB circB; @PostConstruct public void init () {circB.setCircA (this); } public CircularDependencyB getCircB () {return circB; }}
@Component javna klasa CircularDependencyB {private CircularDependencyA circA; privatna niska poruka = ​​"Bok!"; javna praznina setCircA (CircularDependencyA circA) {this.circA = circA; } javni String getMessage () {return poruka; }}

A možemo pokrenuti isti test koji smo prethodno imali, pa provjeravamo da li se iznimka kružne ovisnosti još uvijek ne baca i jesu li ovisnosti pravilno ubrizgane.

4.5. Implementirati ApplicationContextAware i InitializingBean

Ako jedan od graha provodi ApplicationContextAware, grah ima pristup kontekstu Spring i odatle može izvući drugi grah. Provedba InitializingBean naznačujemo da ovaj grah mora izvršiti neke radnje nakon što su postavljena sva njegova svojstva; u ovom slučaju želimo ručno postaviti svoju ovisnost.

Kod našeg graha bio bi:

@Component javna klasa CircularDependencyA implementira ApplicationContextAware, InitializingBean {private CircularDependencyB circB; privatni kontekst ApplicationContext; public CircularDependencyB getCircB () {return circB; } @Override public void afterPropertiesSet () baca iznimku {circB = context.getBean (CircularDependencyB.class); } @Override public void setApplicationContext (final ApplicationContext ctx) baca BeansException {context = ctx; }}
@Component javna klasa CircularDependencyB {private CircularDependencyA circA; privatna niska poruka = ​​"Bok!"; @Autowired javna praznina setCircA (CircularDependencyA circA) {this.circA = circA; } javni String getMessage () {return poruka; }}

Opet, možemo pokrenuti prethodni test i vidjeti da se iznimka ne izbacuje i da test radi kako se očekivalo.

5. U zaključku

Postoji mnogo načina za rješavanje kružnih ovisnosti u proljeće. Prvo što treba uzeti u obzir jest redizajnirati svoj grah tako da nema potrebe za kružnim ovisnostima: oni su obično simptom dizajna koji se može poboljšati.

Ali ako apsolutno trebate imati kružne ovisnosti u svom projektu, možete slijediti neke od ovdje predloženih zaobilaznih rješenja.

Poželjna metoda je korištenje injekcija setera. Ali postoje i druge alternative, koje se uglavnom temelje na zaustavljanju Springa da upravlja inicijalizacijom i ubrizgavanjem graha i na tome da to sami radite koristeći jednu ili drugu strategiju.

Primjeri se mogu naći u projektu GitHub.


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