Dépendance circulaire au printemps

Réponses:

42

Comme les autres réponses l'ont dit, Spring s'en occupe, créant les beans et les injectant selon les besoins.

L'une des conséquences est que l'injection de bean / la définition de propriété peut se produire dans un ordre différent de ce que vos fichiers de câblage XML semblent impliquer. Vous devez donc faire attention à ce que les setters de votre propriété ne procèdent pas à une initialisation qui repose sur l'appel d'autres setters. La façon de gérer cela est de déclarer les beans implémentant l' InitializingBeaninterface. Cela nécessite que vous implémentiez la afterPropertiesSet()méthode, et c'est là que vous effectuez l'initialisation critique. (J'inclus également du code pour vérifier que les propriétés importantes ont bien été définies.)

Stephen C
la source
76

Le manuel de référence Spring explique comment les dépendances circulaires sont résolues. Les beans sont d'abord instanciés, puis injectés les uns dans les autres.

Considérez cette 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;
    }

}

Et une classe similaire 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;
    }

}

Si vous aviez alors ce fichier de configuration:

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

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

Vous verriez la sortie suivante lors de la création d'un contexte à l'aide de cette configuration:

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

Notez que quand aest injecté dans b, an'est pas encore complètement initialisé.

Richard Fearn
la source
26
C'est pourquoi Spring nécessite un constructeur sans arguments ;-)
Chris Thompson
15
Pas si vous utilisez des arguments de constructeur dans vos définitions de bean! (Mais dans ce cas, vous ne pouvez pas avoir de dépendance circulaire.)
Richard Fearn
1
@Richard Fearn Votre message concerne-t-il l'explication du problème plutôt que la solution?
gstackoverflow
4
Si vous essayez d'utiliser l'injection de constructeur, le message d'erreur estorg.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

Dans la base de code avec laquelle je travaille (1 million + lignes de code), nous avons eu un problème avec des temps de démarrage longs, environ 60 secondes. Nous obtenions 12000+ FactoryBeanNotInitializedException .

Ce que j'ai fait, c'est définir un point d'arrêt conditionnel dans 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;
}

où c'est le cas, destroySingleton(beanName)j'ai imprimé l'exception avec le code de point d'arrêt conditionnel:

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

Apparemment, cela se produit lorsque les FactoryBean sont impliqués dans un graphe de dépendance cyclique. Nous l'avons résolu en implémentant ApplicationContextAware et InitializingBean et en injectant manuellement les beans.

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

Cela a réduit le temps de démarrage à environ 15 secondes.

Donc, ne supposez pas toujours que le printemps peut être bon pour résoudre ces références pour vous.

Pour cette raison, je recommanderais de désactiver la résolution de dépendance cyclique avec AbstractRefreshableApplicationContext # setAllowCircularReferences (false) pour éviter de nombreux problèmes futurs.

jontejj
la source
3
Recommandation intéressante. Ma contre-recommandation serait de ne faire cela que si vous soupçonnez que les références circulaires causent un problème de performances. (Ce serait dommage de casser quelque chose qui n'avait pas besoin d'être brisé en essayant de résoudre un problème qui n'avait pas besoin d'être corrigé.)
Stephen C
2
C'est une pente glissante vers l'enfer de la maintenance pour permettre les dépendances circulaires, la refonte de votre architecture à partir de dépendances circulaires peut être vraiment délicate, comme c'était le cas dans notre cas. Cela signifiait à peu près pour nous que nous avions deux fois plus de connexions à la base de données au démarrage que la sessionfactory était impliquée dans la dépendance circulaire. Dans d'autres scénarios, des choses beaucoup plus désastreuses auraient pu se produire en raison de l'instanciation du bean plus de 12 000 fois. Bien sûr, vous devriez écrire vos beans pour qu'ils prennent en charge leur destruction, mais pourquoi autoriser ce comportement en premier lieu?
jontejj
@jontejj, vous méritez un cookie
serprime
14

Problème ->

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

// Provoqué par: org.springframework.beans.factory.BeanCurrentlyInCreationException: Erreur lors de la création du bean avec le nom 'A': Le bean demandé est en cours de création: Y a-t-il une référence circulaire impossible à résoudre?

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

Solution 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 };
}
Akshay N. Shelke
la source
12

Il le fait simplement. Il instancie aet b, et s'injecte l'un dans l'autre (en utilisant leurs méthodes de définition).

Quel est le problème?

skaffman
la source
9
@javaguy: Non, ce ne sera pas le cas.
skaffman
@skaffman uniquement avec la méthode after propertiesSet correcte?
gstackoverflow
6

De la référence de printemps :

Vous pouvez généralement faire confiance à Spring pour faire ce qu'il faut. Il détecte les problèmes de configuration, tels que les références à des beans inexistants et des dépendances circulaires, au moment du chargement du conteneur. Spring définit les propriétés et résout les dépendances le plus tard possible, lorsque le bean est réellement créé.

Earldouglas
la source
6

Le conteneur Spring est capable de résoudre les dépendances circulaires basées sur Setter, mais donne une exception d'exécution BeanCurrentlyInCreationException en cas de dépendances circulaires basées sur Constructor. En cas de dépendance circulaire basée sur Setter, le conteneur IOC le gère différemment d'un scénario typique dans lequel il configurerait entièrement le bean collaborant avant de l'injecter. Par exemple, si Bean A a une dépendance sur Bean B et Bean B sur Bean C, le conteneur initialise complètement C avant de l'injecter dans B et une fois que B est complètement initialisé, il est injecté à A. Mais en cas de dépendance circulaire, on des beans est injecté à l'autre avant d'être complètement initialisé.

Saurav S.
la source
5

Disons que A dépend de B, puis Spring instanciera d'abord A, puis B, puis définira les propriétés de B, puis définira B en A.

Mais que faire si B dépend aussi de A?

Ma compréhension est la suivante: Spring vient de découvrir que A a été construit (le constructeur a été exécuté), mais pas complètement initialisé (toutes les injections ne sont pas effectuées), eh bien, il a pensé, c'est OK, il est tolérable que A ne soit pas complètement initialisé, il suffit de définir ce non- instances A entièrement initialisées dans B pour le moment. Une fois que B est complètement initialisé, il a été défini sur A, et finalement, A a été complètement lancé maintenant.

En d'autres termes, il expose simplement A à B à l'avance.

Pour les dépendances via le constructeur, Sprint lance simplement BeanCurrentlyInCreationException, pour résoudre cette exception, définissez lazy-init sur true pour le bean qui dépend des autres via constructor-arg.

Silencieux
la source
simple et l'une des meilleures explications.
Sritam Jagadev
5

C'est clairement expliqué ici . Merci à Eugen Paraschiv.

La dépendance circulaire est une odeur de conception, corrigez-la ou utilisez @Lazy pour la dépendance qui pose problème pour la contourner.

qui suis je
la source
3

Si vous utilisez généralement l'injection de constructeur et que vous ne voulez pas passer à l'injection de propriété, l'injection de méthode de recherche de Spring permettra à un bean de rechercher paresseusement l'autre et donc de contourner la dépendance cyclique. Voir ici: http://docs.spring.io/spring/docs/1.2.9/reference/beans.html#d0e1161

barclar
la source
3

L'injection de constructeur échoue lorsqu'il existe une dépendance circulaire entre les beans spring. Donc, dans ce cas, nous l'injection Setter aide à résoudre le problème.

Fondamentalement, l'injection de constructeur est utile pour les dépendances obligatoires, pour les dépendances facultatives, il est préférable d'utiliser l'injection de Setter car nous pouvons faire une ré-injection.

Premraj
la source
0

Si deux beans sont dépendants l'un de l'autre, nous ne devons pas utiliser l'injection de constructeur dans les deux définitions de bean. Au lieu de cela, nous devons utiliser l'injection de setter dans n'importe lequel des beans. (bien sûr, nous pouvons utiliser l'injection de setter dans les deux définitions de bean, mais les injections de constructeur dans les deux lancent 'BeanCurrentlyInCreationException'

Reportez-vous à la documentation Spring sur " https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#resources-resource "

Srikant M
la source