Comment Spring résout-il cela: le haricot A dépend du haricot B et le haricot B du haricot A.
Comment Spring résout-il cela: le haricot A dépend du haricot B et le haricot B du haricot A.
Réponses:
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' InitializingBean
interface. 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.)
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 a
est injecté dans b
, a
n'est pas encore complètement initialisé.
org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?
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.
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 };
}
Il le fait simplement. Il instancie a
et b
, et s'injecte l'un dans l'autre (en utilisant leurs méthodes de définition).
Quel est le problème?
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éé.
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é.
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.
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.
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
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.
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 "