Dipendenza circolare in primavera


Risposte:


42

Come hanno detto le altre risposte, Spring se ne prende cura, creando i fagioli e iniettandoli secondo necessità.

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


76

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

Considera questa classe:

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 classe 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 poi avevi questo file di configurazione:

<bean id="a" class="mypackage.A">
    <property name="b" ref="b" />
</bean>

<bean id="b" class="mypackage.B">
    <property name="a" ref="a" />
</bean>

Vedrai il seguente output 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

Notare che quando aviene iniettato b, anon è ancora completamente inizializzato.


26
Questo è il motivo per cui Spring richiede un costruttore senza argomenti ;-)
Chris Thompson

15
No, se usi gli argomenti del costruttore nelle definizioni dei tuoi bean! (Ma in quel caso non puoi avere una dipendenza circolare.)
Richard Fearn

1
@Richard Fearn Il tuo post riguarda la spiegazione del problema piuttosto che la fornitura della soluzione?
gstackoverflow

4
Se si tenta di utilizzare l'iniezione del costruttore, il messaggio di errore èorg.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?
X. Wo Satuk

19

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

Quello che ho fatto è stato impostare un punto di interruzione 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 destroySingleton(beanName)ho stampato l'eccezione con il codice del punto di interruzione condizionale:

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

Apparentemente questo accade quando i FactoryBean sono coinvolti in un grafico di dipendenza ciclico. Lo abbiamo risolto implementando ApplicationContextAware e InitializingBean e inserendo 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 dare sempre per scontato che la primavera possa essere utile per risolvere questi riferimenti per te.

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


3
Raccomandazione interessante. La mia contro raccomandazione sarebbe di farlo solo se sospetti che i riferimenti circolari stiano causando un problema di prestazioni. (Sarebbe un peccato rompere qualcosa che non ha bisogno di essere rotto cercando di risolvere un problema che non ha bisogno di essere risolto.)
Stephen C

2
È un pendio scivoloso verso l'inferno della manutenzione per consentire dipendenze circolari, riprogettare la tua architettura da dipendenze circolari può essere davvero complicato, come nel nostro caso. Ciò che più o meno significava per noi era che durante l'avvio abbiamo ottenuto il doppio delle connessioni al database di quante la sessionfactory fosse coinvolta nella dipendenza circolare. In altri scenari sarebbero potute accadere cose molto più disastrose a causa dell'istanza del bean oltre 12000 volte. Certo, dovresti scrivere i tuoi fagioli in modo che supportino la loro distruzione, ma perché consentire questo comportamento in primo luogo?
jontejj

@jontejj, ti meriti un biscotto
serprime

14

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 };
 }

// Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Errore durante la creazione del bean con nome "A": Il bean richiesto è attualmente in fase di creazione: C'è 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 };
}

12

Lo fa e basta. Istanzia ae b, e inietta l'uno nell'altro (usando i loro metodi setter).

Qual è il problema?


9
@javaguy: No, non lo farà.
skaffman

@skaffman solo con l'utilizzo del metodo after propertiesSet corretto?
gstackoverflow

6

Dal riferimento di primavera :

In genere 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 possibile, quando il bean viene effettivamente creato.


6

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 sul costruttore. In caso di dipendenza circolare basata su Setter, il contenitore IOC lo 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 da Bean C, il contenitore inizializza completamente C prima di iniettarlo in B e una volta che B è completamente inizializzato viene iniettato in A. Ma in caso di dipendenza circolare, uno dei fagioli viene iniettato all'altro prima che sia completamente inizializzato.


5

Diciamo che A dipende da B, quindi Spring creerà prima un'istanza di A, quindi B, quindi imposterà le proprietà per B, quindi imposterà 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, pensava, va bene, è tollerabile che A non sia completamente inizializzato, basta impostare questo non- istanze A completamente inizializzate in B per ora. Dopo che B è stato completamente inizializzato, è stato impostato in A e, infine, A è stato avviato completamente ora.

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

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


semplice e una delle migliori spiegazioni.
Sritam Jagadev

5

È chiaramente spiegato qui . Grazie a Eugen Paraschiv.

La dipendenza circolare è un odore di design, risolvilo o usa @Lazy per la dipendenza che causa problemi per risolverlo.



3

L'iniezione del costruttore non riesce quando è presente una dipendenza circolare tra i bean primaverili. Quindi in questo caso noi Setter injection aiuta a risolvere il problema.

Fondamentalmente, l'iniezione del costruttore è utile per le dipendenze obbligatorie, per le dipendenze opzionali è meglio usare l'iniezione di Setter perché possiamo fare la re-iniezione.


0

Se due bean dipendono l'uno dall'altro, non dovremmo usare l'iniezione nel costruttore in entrambe le definizioni dei bean. Invece dobbiamo usare l'iniezione di setter in uno qualsiasi dei fagioli. (ovviamente possiamo usare l'iniezione setter in entrambe le definizioni dei bean, ma le iniezioni del costruttore in entrambe lanciano 'BeanCurrentlyInCreationException'

Fare riferimento al documento di primavera su " https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#resources-resource "

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.