Dipendenza circolare in spring

Come risolve questo problema: il bean A dipende dal bean B e dal bean B sul bean A.

Come le altre risposte hanno detto, Spring si prende cura di esso, creando i fagioli e iniettandoli come richiesto.

Una delle conseguenze è che l’impostazione del bean injection / property potrebbe verificarsi in un ordine diverso da quello che i file di cablaggio XML sembrano implicare. Quindi devi stare attento che i tuoi setter non facciano un’inizializzazione che si basa su altri setter già chiamati. Il modo per risolvere questo problema è dichiarare i bean come implementanti l’interfaccia InitializingBean . Ciò richiede l’implementazione del metodo afterPropertiesSet() , e qui si esegue l’inizializzazione critica. (Includo anche il codice per verificare che siano state effettivamente impostate le proprietà importanti.)

Il manuale di riferimento Spring spiega come vengono risolte le dipendenze circolari. I fagioli vengono prima istanziati, quindi iniettati l’uno nell’altro.

Considera questa class:

 package mypackage; public class A { public A() { System.out.println("Creating instance of A"); } private B b; public void setB(B b) { System.out.println("Setting property b of A instance"); this.b = b; } } 

E una class simile B :

 package mypackage; public class B { public B() { System.out.println("Creating instance of B"); } private A a; public void setA(A a) { System.out.println("Setting property a of B instance"); this.a = a; } } 

Se hai avuto questo file di configurazione:

       

Vedrai il seguente risultato quando crei un contesto usando questa configurazione:

 Creating instance of A Creating instance of B Setting property a of B instance Setting property b of A instance 

Si noti che quando a viene iniettato in b , a non è ancora completamente inizializzato.

Nel codebase con cui sto lavorando (1 milione di righe + di codice) abbiamo avuto un problema con tempi di avvio lunghi, circa 60 secondi. Abbiamo ricevuto 12000+ FactoryBeanNotInitializedException .

Quello che ho fatto è stato impostare un breakpoint condizionale in AbstractBeanFactory # doGetBean

 catch (BeansException ex) { // Explicitly remove instance from singleton cache: It might have been put there // eagerly by the creation process, to allow for circular reference resolution. // Also remove any beans that received a temporary reference to the bean. destroySingleton(beanName); throw ex; } 

dove fa destroySingleton(beanName) Ho stampato l’eccezione con il codice del breakpoint condizionale:

  System.out.println(ex); return false; 

Apparentemente ciò accade quando i FactoryBean sono coinvolti in un grafico di dipendenza ciclica. Lo abbiamo risolto implementando ApplicationContextAware e InitializingBean e iniettando manualmente i bean.

 import org.springframework.beans.BeansException; import org.springframework.beans.factory.InitializingBean; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; public class A implements ApplicationContextAware, InitializingBean{ private B cyclicDepenency; private ApplicationContext ctx; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { ctx = applicationContext; } @Override public void afterPropertiesSet() throws Exception { cyclicDepenency = ctx.getBean(B.class); } public void useCyclicDependency() { cyclicDepenency.doSomething(); } } 

Ciò ha ridotto il tempo di avvio a circa 15 secondi.

Quindi non sempre supponiamo che la spring possa essere bravo a risolvere questi riferimenti per te.

Per questo motivo, raccomanderei di disabilitare la risoluzione delle dipendenze cicliche con AbstractRefreshableApplicationContext # setAllowCircularReferences (false) per prevenire molti problemi futuri.

Lo fa e basta Crea un’istanza a e b , e inietta ciascuno nell’altro (usando i loro metodi setter).

Qual è il problema?

Dal riferimento di spring :

Generalmente puoi fidarti di Spring per fare la cosa giusta. Rileva problemi di configurazione, come riferimenti a bean inesistenti e dipendenze circolari, al momento del caricamento del contenitore. Spring imposta le proprietà e risolve le dipendenze il più tardi ansible, quando il bean viene effettivamente creato.

Il contenitore Spring è in grado di risolvere le dipendenze circolari basate su Setter ma fornisce un’eccezione di runtime BeanCurrentlyInCreationException in caso di dipendenze circolari basate su Constructor. In caso di dipendenza circolare basata su Setter, il contenitore IOC la gestisce in modo diverso da uno scenario tipico in cui configurerebbe completamente il bean collaborante prima di iniettarlo. Ad esempio, se Bean A ha una dipendenza da Bean B e Bean B su Bean C, il contenitore inizializza completamente C prima di iniettarlo in B e una volta che B è inizializzato completamente viene iniettato in A. Ma in caso di dipendenza circolare, uno dei fagioli viene iniettato all’altro prima che sia completamente inizializzato.

È chiaramente spiegato qui . Grazie a Eugen Paraschiv.

La dipendenza circolare è un odore di progettazione, o risolvila o usa @Lazy per la dipendenza che causa il problema di risolvere il problema.

Se in genere si utilizza l’iniezione del costruttore e non si desidera passare all’iniezione di proprietà, il metodo di ricerca di Spring -injection consentirà a un bean di cercare pigramente l’altro e quindi di risolvere il problema con la dipendenza ciclica. Vedi qui: http://docs.spring.io/spring/docs/1.2.9/reference/beans.html#d0e1161

Dire A dipende da B, quindi Spring prima istanzia A, poi B, quindi imposta le proprietà per B, quindi imposta B in A.

Ma cosa succede se B dipende anche da A?

La mia comprensione è: Spring ha appena scoperto che A è stato costruito (costruttore eseguito), ma non completamente inizializzato (non tutte le iniezioni fatte), beh, pensò, è OK, è tollerabile che A non sia completamente inizializzato, basta impostare questo non- A istanze completamente inizializzate in B per ora. Dopo che B è stato completamente inizializzato, è stato impostato su A e, infine, A è stato avviato completamente ora.

In altre parole, espone semplicemente A a B in anticipo.

Per le dipendenze tramite il costruttore, Sprint lancia semplicemente BeanCurrentlyInCreationException, per risolvere questa eccezione, impostare lazy-init su true per il bean che dipende dagli altri tramite il modo costroter-arg.

Problema ->

 Class A { private final B b; // must initialize in ctor/instance block public A(B b) { this.b = b }; } Class B { private final A a; // must initialize in ctor/instance block public B(A a) { this.a = a }; } 

// Causato da: org.springframework.beans.factory.BeanCurrentlyInCreationException: Errore durante la creazione di bean con nome “A”: bean richiesto è attualmente in fase di creazione: esiste un riferimento circolare irrisolvibile?

Soluzione 1 ->

 Class A { private B b; public A( ) { }; //getter-setter for B b } Class B { private A a; public B( ) { }; //getter-setter for A a } 

Soluzione 2 ->

 Class A { private final B b; // must initialize in ctor/instance block public A(@Lazy B b) { this.b = b }; } Class B { private final A a; // must initialize in ctor/instance block public B(A a) { this.a = a }; } 

Utilizzando Setter Injection o Field Injection o usando @Lazy per la dipendenza.

Se due bean sono dipendenti l’uno dall’altra, non dovremmo usare l’iniezione di Constructor in entrambe le definizioni dei bean. Invece dobbiamo usare l’iniezione setter in uno qualsiasi dei fagioli. (ovviamente possiamo usare setter injection n sia le definizioni bean, ma le iniezioni del costruttore in entrambi i passi “BeanCurrentlyInCreationException”

Consultare il documento Spring su ” https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#resources-resource

L’iniezione del costruttore non riesce quando c’è una dipendenza circolare tra i fagioli a molla. Quindi in questo caso l’iniezione di Setter aiuta a risolvere il problema.

Fondamentalmente, la Iniezione del Costruttore è utile per le dipendenze Obbligatorie, per le dipendenze opzionali meglio usare l’iniezione del Setter perché possiamo fare la re-iniezione.