123 votes

Dépendance circulaire au printemps

Comment Spring résout-il ceci : le bean A est dépendant du bean B, et le bean B du bean A.

8 votes

0 votes

Un autre article utile expliquant comment les dépendances circulaires se produisent : octoperf.com/blog/2018/02/15/dépendances printanières-circulaires

83voto

Richard Fearn Points 11631

El Manuel de référence sur les ressorts 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 verrez le résultat suivant lors de la création d'un contexte utilisant cette configuration :

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

Notez que lorsque a est injecté dans b , a n'est pas encore complètement initialisé.

29 votes

C'est pourquoi Spring exige un constructeur sans arguments ;-)

15 votes

Pas si vous utilisez des arguments de constructeur dans vos définitions de haricots ! (Mais dans ce cas, vous ne pouvez pas avoir de dépendance circulaire).

1 votes

@Richard Fearn Votre message porte-t-il sur l'explication du problème plutôt que sur la fourniture d'une solution ?

47voto

Stephen C Points 255558

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

L'une des conséquences est que l'injection de beans et la définition de propriétés peuvent se produire dans un ordre différent de celui que vos fichiers de câblage XML semblent impliquer. Vous devez donc faire attention à ce que vos setters de propriétés ne fassent pas d'initialisation qui dépende d'autres setters déjà appelés. La manière de traiter ce problème est de déclarer les beans comme implémentant la méthode InitializingBean l'interface. Pour cela, vous devez implémenter l'interface afterPropertiesSet() et c'est là que vous faites l'initialisation critique. (J'inclus également du code pour vérifier que les propriétés importantes ont bien été définies).

23voto

jontejj Points 725

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

Ce que j'ai fait, c'est mettre un point d'arrêt conditionnel en 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ù il le fait destroySingleton(beanName) J'ai imprimé l'exception avec un code de point d'arrêt conditionnel :

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

Apparemment, cela arrive quand FactoryBean sont impliqués dans un graphe de dépendance cyclique. Nous l'avons résolu en implémentant ApplicationContextAware y InitializingBean et l'injection manuelle des haricots.

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.

Ne partez donc pas toujours du principe que le printemps est capable de résoudre ces références pour vous.

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

3 votes

Une recommandation intéressante. Ma contre recommandation serait de ne le faire que si vous suspect que les références circulaires causent un problème de performance. (Il serait dommage de casser quelque chose qui n'a pas besoin d'être cassé en essayant de corriger un problème qui n'a pas besoin d'être corrigé).

2 votes

Autoriser les dépendances circulaires est une pente glissante vers l'enfer de la maintenance. Reconcevoir votre architecture à partir de dépendances circulaires peut être vraiment délicat, comme ce fut le cas pour nous. Ce que cela signifiait en gros pour nous, c'est que nous avions deux fois plus de connexions à la base de données pendant le démarrage, car la sessionfactory était impliquée dans la dépendance circulaire. Dans d'autres scénarios, des choses bien plus désastreuses auraient pu se produire du fait que le bean a été instancié plus de 12 000 fois. Bien sûr, vous devriez écrire vos beans de manière à ce qu'ils puissent être détruits, mais pourquoi autoriser ce comportement en premier lieu ?

0 votes

@jontejj, vous méritez un cookie.

14voto

skaffman Points 197885

Il le fait tout simplement. Il instancie a y b et injecte chacun d'entre eux dans l'autre (en utilisant leurs méthodes setter).

Quel est le problème ?

0 votes

@skaffman seul moyen d'utiliser correctement la méthode propertiesSet après ?

7voto

James Points 5907

Desde el Référence du printemps :

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X