Pour résumer
Grâce au cache à trois niveaux et au mécanisme de "pré-exposition", Spring fonctionne avec le principe de référence d'objet de Java pour résoudre parfaitement le problème de dépendance circulaire dans certains cas!
N'utilisez pas l'injection de dépendances basée sur un constructeur, elle peut être résolue de la manière suivante:
- Utilisez l'annotation @Autowired sur le terrain pour laisser Spring décider d'injecter au bon moment
- Injection de dépendances basée sur la méthode du setter
Préface
Dans le travail réel, souvent en raison d'une mauvaise conception ou de divers facteurs, l'interdépendance entre les classes est souvent causée. Ces classes peuvent ne pas causer de problèmes lorsqu'elles sont utilisées seules, mais peuvent lever des exceptions telles que BeanCurrentlyInCreationException lors de l'utilisation de Spring pour la gestion . Lorsque cette exception est levée, cela signifie que Spring ne peut pas résoudre la dépendance circulaire. Cet article explique brièvement la solution de Spring aux dépendances circulaires.
Qu'est-ce que la dépendance circulaire?
La dépendance circulaire est en fait une référence circulaire, c'est-à-dire que deux ou plusieurs beans se tiennent mutuellement et forment finalement une boucle fermée. Par exemple, A dépend de B, B dépend de C et C dépend de A. Comme indiqué ci-dessous:
Prérequis pour la génération et la résolution des dépendances circulaires
Il peut y avoir de nombreuses situations pour la génération de dépendances circulaires, par exemple:
- La méthode de construction de A repose sur l'objet d'instance de B et la méthode de construction de B repose sur l'objet d'instance de A
- La méthode de construction de A repose sur l'objet d'instance de B, et en même temps un certain champ ou setter de B a besoin de l'objet d'instance de A, et vice versa
- Un certain champ ou setter de A dépend de l'objet d'instance de B, tandis qu'un certain champ ou setter de B dépend de l'objet d'instance de A, et vice versa
Bien entendu, la résolution par Spring des dépendances circulaires n'est pas inconditionnelle. La première condition préalable est que la portée soit singleton et ne spécifie pas explicitement un objet qui n'a pas besoin de résoudre les dépendances circulaires et nécessite que l'objet n'ait pas été mandaté. Dans le même temps, Spring n'est pas omnipotent pour résoudre les dépendances circulaires. Les trois cas ci-dessus ne peuvent résoudre que les deux derniers . Le premier cas de dépendance mutuelle dans la méthode de construction est également incapable de récupérer. La conclusion est ici d'abord, jetons un coup d'œil à la solution de Spring, connaissant la solution, vous pouvez comprendre pourquoi la première situation ne peut pas être résolue.
La solution de Spring aux dépendances circulaires
Le problème est prolongé: le cycle de vie de Spring Bean
La base théorique de la dépendance circulaire de Spring est en fait que Java est basé sur le passage de référence. Lorsque nous obtenons la référence de l'objet, le champ ou la propriété de l'objet peut être défini ultérieurement.
L'initialisation des objets Spring singleton peut en fait être divisée en trois étapes:
- createBeanInstance , instanciation, consiste en fait à appeler le constructeur correspondant pour construire l'objet. À ce stade , seul le constructeur est appelé et la propriété spécifiée dans spring xml n'est pas renseignée.
- populateBean , remplissez les propriétés, cette étape remplit les propriétés spécifiées dans le xml de printemps
- initializeBean , appelez la méthode init spécifiée dans spring xml ou la méthode AfterPropertiesSet
. Les étapes où la dépendance circulaire se produit sont concentrées dans les première et deuxième étapes.
Cache de niveau 3
Pour les objets singleton, dans tout le cycle de vie du conteneur de Spring, il existe un et un seul objet. Il est facile de penser que cet objet devrait exister dans Cache. Spring utilise largement Cache pour résoudre le problème de dépendance circulaire. Même le "cache à trois niveaux" est utilisé.
"Cache à trois niveaux" se réfère principalement à
/** Cache of singleton objects: bean name --> bean instance */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);
/** Cache of singleton factories: bean name --> ObjectFactory */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);
/** Cache of early singleton objects: bean name --> bean instance */
private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);
Littéralement:
- Niveau 1: singletonObjects fait référence au cache des objets singleton
- Niveau 2: singletonFactories fait référence au cache de la fabrique d'objets singleton
- Niveau 3: earlySingletonObjects fait référence au cache des objets singleton exposés à l'avance
- Les trois caches ci-dessus constituent un cache à trois niveaux, et Spring utilise ces caches à trois niveaux pour résoudre intelligemment le problème de dépendance circulaire
Solution
Rappelant le processus de création de Bean dans l'article précédent, Spring essaiera d'abord de le récupérer à partir du cache. Ce cache fait référence à singletonObjects. La principale méthode à appeler est:
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return (singletonObject != NULL_OBJECT ? singletonObject : null);}
Expliquez d'abord les deux paramètres:
- isSingletonCurrentlyInCreation Détermine si l'objet singleton correspondant est en cours de création. Lorsque l'objet singleton n'est pas complètement initialisé (par exemple, le constructeur défini par A dépend de l'objet B, vous devez d'abord créer l'objet B ou vous fier à l'objet B dans le populatebean, donc commencez par créer l'objet B, à ce moment-là, A est en cours de création)
- allowEarlyReference Est-il autorisé à obtenir des objets de singletonFactories via getObject
En analysant l'ensemble du processus de getSingleton, Spring essaie d'abord d'obtenir de singletonObjects (cache de premier niveau), s'il ne peut pas l'obtenir et que l'objet est en cours de création, il essaie de l'obtenir à partir de earlySingletonObjects (cache de second niveau), s'il est toujours ne peut pas être obtenu et autorise singletonFactories Obtenu par getObject, puis obtenu par singletonFactory.getObject () (cache de troisième niveau). Si tu l'as alors
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
Ensuite, supprimez le singletonFactory correspondant et placez le singletonObject dans earlySingletonObjects, qui consiste en fait à mettre à niveau le cache de troisième niveau vers le cache de deuxième niveau!
L'astuce de Spring pour résoudre les dépendances circulaires réside dans le cache singletonFactories. Le cache est de type ObjectFactory, défini comme suit:
public interface ObjectFactory<T> {
T getObject() throws BeansException;}
Dans le processus de création du bean, il existe deux classes internes anonymes importantes qui implémentent cette interface. Un endroit est
new ObjectFactory<Object>() {
@Override public Object getObject() throws BeansException {
try {
return createBean(beanName, mbd, args);
} catch (BeansException ex) {
destroySingleton(beanName);
throw ex;
} }
Comme mentionné ci-dessus, Spring l'utilise pour créer des beans (ce n'est vraiment pas clair ...)
L'autre endroit est:
addSingletonFactory(beanName, new ObjectFactory<Object>() {
@Override public Object getObject() throws BeansException {
return getEarlyBeanReference(beanName, mbd, bean);
}});
Voici la clé pour résoudre les dépendances circulaires:
Ce code se produit après la première étape [ createBeanInstance ], ce qui signifie que l'objet singleton a été créé à ce moment
Cet objet a été produit, bien qu'il ne soit pas parfait, la deuxième étape [ populateBean ] et la troisième étape [ initializeBean ] d' initialisation n'ont pas été effectuées , mais il a été reconnu (selon la référence de l'objet peut être localisé dans le tas Object), donc Spring exposera cet objet à l'avance pour que tout le monde le sache et l'utilise.
Quels sont les avantages de faire cela? Analysons la dépendance circulaire de "un certain champ ou setter de A dépend de l'objet d'instance de B, tandis qu'un certain champ ou setter de B dépend de l'objet d'instance de A". Un premier a terminé la première étape d'initialisation et s'est exposé à singletonFactories à l'avance. À ce moment, la deuxième étape d'initialisation a été effectuée et a constaté qu'il s'appuyait sur l'objet B. À ce moment, il a essayé d'obtenir (B) et a trouvé que B n'a pas encore été créé., Alors prenez le processus de création, B a constaté qu'il s'appuyait sur l'objet A lors de l'initialisation de la première étape, il a donc essayé d'obtenir (A), essayé les singletonObjects de cache de premier niveau (certainement pas, car A a pas été complètement initialisé), essayé le cache de deuxième niveau earlySingletonObjects (Nor), essayez le cache à trois niveaux singletonFactories, car A s'est exposé à l'avance via ObjectFactory, afin que B puisse obtenir un objet via ObjectFactory.getObject (bien que A n'ait pas été initialisé complètement, c'est mieux que rien), B prend Après avoir atteint l'objet A, les phases d'initialisation 1, 2 et 3 sont terminées avec succès, et après l'initialisation complète, il se place dans les singletonObjects du cache de premier niveau. À ce moment, retournez à A, A peut obtenir l'objet de B à ce moment pour terminer avec succès ses propres phases d'initialisation 2, 3, et enfin A termine également l'initialisation, grandit et entre dans les singletonObjects du cache de premier niveau, et heureusement, puisque B a obtenu la référence d'objet de A, l'objet A que B détient maintenant est également transformé à la perfection! Tout est si magique! !
Lorsque vous connaissez ce principe, vous devez savoir pourquoi Spring ne peut pas résoudre le problème de "La méthode de construction de A dépend de l'objet d'instance de B, et la méthode de construction de B dépend de l'objet d'instance de A"!
Analyse du rapport d'erreur de dépendance circulaire basé sur le constructeur
Le conteneur Spring placera chaque identifiant Bean en cours de création dans un "pool Bean actuellement créé". L'identifiant Bean restera dans ce pool pendant le processus de création, donc si vous vous trouvez dans l'exception "BeanCurrentlyInCreationException" sera levé dans le "Bean actuellement créé Pool »pour indiquer une dépendance circulaire et le Bean créé sera supprimé du« Pool de Bean actuellement créé ».
Le conteneur Spring crée d'abord un singleton A, A dépend de B, puis place A dans le "pool de Bean actuellement créé", puis crée B, et B dépend de C, puis place B dans le "pool de Bean actuellement créé", et crée ensuite C, C dépendent de A, mais à ce moment A est déjà dans le pool, donc une erreur sera signalée, car les Beans du pool ne sont pas initialisés, donc ils s'appuieront sur l'erreur.