Analyse approfondie des dépendances circulaires Spring au niveau du code source | Équipe technique JD Cloud

**以下举例皆针对单例模式讨论**

Référence graphique https://www.processon.com/view/link/60e3b0ae0e3e74200e2478ce

1. Comment Spring crée-t-il des haricots ?

Pour un bean singleton, il y a un et un seul objet dans tout le cycle de vie du conteneur Spring.

Lors du processus de création de beans, Spring utilise le cache à trois niveaux, qui est défini dans DefaultSingletonBeanRegistry.java :

    /** Cache of singleton objects: bean name to bean instance. */
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
​
    /** Cache of singleton factories: bean name to ObjectFactory. */
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
​
    /** Cache of early singleton objects: bean name to bean instance. */
    private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);

Prenez OneBean sous le package com.gyh.general comme exemple, déboguez le processus de démarrage de springboot et analysez comment spring crée des beans.

Reportez-vous au processus de création de haricots au printemps dans la figure . Les étapes les plus critiques sont :

1. getSingleton(beanName, true)Rechercher tour à tour des objets bean dans les caches de premier, deuxième et troisième niveau, et revenir directement (tôt) s'il y a des objets dans le cache ;

2. createBeanInstance(beanName, mbd, args)Choisissez un constructeur approprié, nouvel objet d'instance (instance), à ​​ce moment, les attributs dépendants de l'instance sont toujours nuls, ce qui est un produit semi-fini ;

3. singletonFactories.put(beanName, oneSingletonFactory)Utilisez l'instance de l'étape précédente pour créer une singletonFactory et placez-la dans le cache de troisième niveau ;

4. populateBean(beanName, mbd, instanceWrapper)Remplissez le bean : créez un objet ou attribuez une valeur à la propriété définie par le bean ;

5. initializeBean("one",oneInstance, mbd)Initialiser le bean : initialiser ou autrement traiter le bean, par exemple en générant un objet proxy (proxy) ;

6. getSingleton(beanName, false)Rechercher tour à tour dans les caches primaire et secondaire pour vérifier s'il y a des objets générés à l'avance du fait de dépendances circulaires, et si oui, s'ils sont cohérents avec les objets initialisés ;

2. Comment Spring résout-il les dépendances circulaires ?

Prenez OneBean et TwoBean sous le package com.gyh.circular.threeCache comme exemple, les deux Beans dépendent l'un de l'autre (c'est-à-dire qu'ils forment une boucle fermée).

En se référant au processus de résolution de la dépendance circulaire de Spring dans la figure , nous pouvons voir que Spring utilise objectFactory dans le cache à trois niveaux pour générer et renvoyer un objet précoce, et exposer l'adresse précoce à l'avance pour une autre injection de dépendance d'objet afin de résoudre le problème. problème de dépendance circulaire.

3. Quelles dépendances circulaires Spring ne peut-il pas résoudre ?

3.1 L' annotation @Async est utilisée dans la boucle

3.1.1 Pourquoi une erreur est-elle signalée lorsque @Async est utilisé dans la boucle ?

Prenez OneBean et TwoBean sous le package com.gyh.circular.async comme exemple. Les deux beans dépendent l'un de l'autre et la méthode dans oneBean utilise l' annotation @Async . À ce stade, le démarrage de Spring échoue et l'erreur message est :org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a.one': Bean with name 'a.one' has been injected into other beans [a.two] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using 'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.

Et à travers le code de débogage, on constate que l'emplacement de rapport d'erreur se trouve dans la méthode AbstractAutowireCapableBeanFactory#doCreateBean, car earlySingletonReference != null et exposeObject != bean, ce qui entraîne une erreur.

Combiné avec le ressort dans l'organigramme pour résoudre les dépendances circulaires et l'image ci-dessus, nous pouvons voir :

1. Le bean de la ligne 1 est l'instance créée par createBeanInstance (adresse1)

2. L'objet exposé en ligne 2 est l'objet proxy (adresse2) généré après initializeBean

3. EarlySingletonReference à la ligne 3 est l'objet créé lorsque getEarlyBeanReference [l'adresse ici est la même que bean (address1)]

La raison sous-jacente est la suivante : Auparavant, TwoBean s'appuyait déjà sur l'objet earlySingletonReference dont l'adresse était address1 lorsqu'il était populateBean, mais à ce moment-là, OneBean renvoyait un nouvel objet dont l'adresse était address2 après initializeBean, ce qui empêchait spring de savoir quelle était la version finale de le bean, il a donc signalé une erreur.

Comment earlySingletonReference est généré, reportez-vous au processus getSingleton("one", true).

3.1.2 Une erreur sera-t-elle signalée si @Async est utilisé dans la boucle ?

Prenez toujours OneBean et TwoBean sous le package com.gyh.circular.async comme exemple. Les deux beans dépendent l'un de l'autre, de sorte que la méthode dans TwoBean (et non OneBean) utilise l' annotation @Async . À ce stade, le ressort est démarré avec succès et aucune erreur n'est signalée.

Le code de débogage montre que bien que TwoBean utilise l'annotation @Async, son earlySingletonReference = null ; il ne provoquera donc pas d'erreur.

La raison sous-jacente est : OneBean est créé en premier, et TwoBean est créé plus tard. Dans tout le lien, l'objectFactory de TwoBean n'a pas été recherché dans le cache de troisième niveau. (OneBean a été trouvé deux fois pendant le processus de création, c'est-à-dire un -> deux -> un ; lors de la création de TwoBean, il n'a été trouvé qu'une seule fois, c'est-à-dire deux -> un.)

Il peut être obtenu à partir de ceci : @Async provoque des conditions de rapport d'erreur de dépendance circulaire :

1. Les beans de la dépendance circulaire utilisent l'annotation @Async

2. Et ce bean est créé avant les autres beans de la boucle.

3. Remarque : Un bean peut exister dans plusieurs cycles en même temps ; tant qu'il s'agit du premier bean créé dans un certain cycle, une erreur sera signalée.

3.1.3 Pourquoi @Transactional est-il utilisé dans la boucle et aucune erreur n'est signalée ?

On sait que Spring générera également un objet proxy pour un bean annoté avec @Transactional, mais pourquoi ce type de bean ne génère-t-il pas d'erreur lorsqu'il est dans une boucle ?

Prenez OneBean et TwoBean sous le package com.gyh.circular.transactional comme exemple. Les deux Beans dépendent l'un de l'autre et la méthode dans OneBean utilise l'annotation @Transactional. Spring démarre correctement et aucune erreur n'est signalée.

Le code de débogage montre que dans le processus de génération de OneBean, bien que earlySingletonReference != null, l'objet exposé après initializeBean a la même adresse que l'instance d'origine (c'est-à-dire qu'aucun proxy n'est généré pour l'instance dans l'étape initializeBean), donc pas d'erreur sera signalé.

3.1.4 Pourquoi les mêmes agents produisent-ils deux phénomènes différents ?

Des objets proxy sont également générés et participent également à des dépendances circulaires. La raison des différents phénomènes est que lorsqu'ils sont dans des dépendances circulaires, les nœuds qui génèrent des proxys sont différents :

1. @Transactional génère un proxy lors de getEarlyBeanReference et expose l'adresse après le proxy (c'est-à-dire l'adresse finale) à l'avance ;

2. @Async génère un proxy lors de l'initialisation de Bean, ce qui fait que l'adresse exposée à l'avance n'est pas l'adresse finale, ce qui entraîne une erreur.

Pourquoi @Async ne peut-il pas générer de proxy lorsque getEarlyBeanReference ? En comparant le processus de code exécuté par les deux, on constate que :

Les deux encapsulent l'objet d'instance d'origine dans la méthode AbstractAutoProxyCreator#getEarlyBeanReference, comme illustré dans la figure ci-dessous

Lorsque le bean utilisant @Transactional reçoit un conseil lors de la création du proxy, le proxy de l'objet proxy est généré immédiatement.

Cependant, le bean utilisant @Async ne reçoit pas de conseils lors de la création d'un proxy et ne peut pas être proxy.

3.1.5 Pourquoi @Async ne peut pas renvoyer un avis lorsque getEarlyBeanReference ?

Dans la méthode AbstractAutoProxyCreator#getAdvicesAndAdvisorsForBean, les principales choses à faire sont :

1. Trouvez tous les conseillers dans le conteneur de printemps actuel

2. Renvoyer tous les conseillers qui s'adaptent au haricot actuel

Les conseillers renvoyés dans la première étape incluent BeanFactoryCacheOperationSourceAdvisor et BeanFactoryTransactionAttributeSourceAdvisor, et il n'y a pas de conseiller traitant d'Async.

Allez au fond des choses et découvrez pourquoi la première étape ne revient pas à traiter avec le conseiller lié à Async ?

Il est connu que l'utilisation de @Async @Transactional @Cacheable doit être activée à l'avance, c'est-à-dire que @EnableAsync, @EnableTransactionManagement, @EnableCaching doit être marqué à l'avance.

En prenant @EnableTransactionManagement et @EnableCaching comme exemples, la classe Selector est introduite dans sa définition d'annotation, et la classe Configuration est introduite dans le Selector. Dans la classe Configuration, le Advisor correspondant est créé et placé dans le conteneur Spring, donc la première étape peuvent être obtenus Ces deux conseillers.

La classe de configuration introduite dans la définition de @EnableAsync crée AsyncAnnotationBeanPostProcessor au lieu d'un conseiller, donc il ne sera pas obtenu dans la première étape, donc le bean de @Async ne sera pas proxy dans cette étape.

3.2 Dépendances circulaires causées par les constructeurs

Prenez OneBean et TwoBean sous le package com.gyh.circular.constructor comme exemple. Les constructeurs des deux classes dépendent l'un de l'autre. Lorsque Spring démarre, une erreur est signalée :org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'c.one': Requested bean is currently in creation: Is there an unresolvable circular reference?

Le code de débogage montre que les deux beans sont tombés dans une boucle infinie lorsqu'ils sont basés sur la nouvelle instance du constructeur et ne peuvent pas exposer les adresses disponibles à l'avance, ils ne peuvent donc que signaler une erreur.

4. Comment résoudre l'erreur de dépendance circulaire ci-dessus ?

1. Au lieu de @Async, placez les méthodes qui nécessitent des opérations asynchrones dans le pool de threads pour exécution. (recommander)

2. Proposez la méthode marquée avec @Async. (recommander)

3. Proposez la méthode utilisant @Async dans une classe distincte, qui n'effectue que le traitement asynchrone et ne crée pas d'autres dépendances métier, c'est-à-dire pour éviter la formation de dépendances circulaires, résolvant ainsi le problème du rapport d'erreur. Voir le package com.gyh.circular.async.extract.

4. Essayez de ne pas utiliser d'objets dépendants du constructeur. (recommander)

5. Rompre le cycle (non recommandé) ne forme pas une boucle fermée Avant le développement, planifiez les dépendances d'objets et les chaînes d'appel de méthodes, et essayez de ne pas utiliser de dépendances circulaires. (difficile, le développement itératif continuant à changer, il est susceptible de générer des cycles)

6. Casser l'ordre de création (non recommandé)

7. Parce que la classe annotée avec @Async signalera une erreur lorsqu'elle est créée avant d'autres classes dans la dépendance circulaire, alors trouvez un moyen de faire en sorte que cette classe ne soit pas créée avant d'autres classes, et ce problème peut également être résolu, comme : @DependsOn, @ Lazy

Auteur : Guo Yanhong, Jingdong Technology

Source : Communauté de développeurs JD Cloud

Huawei a officiellement publié HarmonyOS 4 miniblink version 108 compilée avec succès. Bram Moolenaar, le père de Vim, le plus petit noyau Chromium au monde, est décédé des suites d'une maladie . ChromeOS a divisé le navigateur et le système d'exploitation en une station indépendante Bilibili (Bilibili) et s'est à nouveau effondré . HarmonyOS NEXT : use fullLe noyau auto-développé Nim v2.0 est officiellement sorti, et le langage de programmation impératif Visual Studio Code 1.81 est sorti.La capacité de production mensuelle de Raspberry Pi a atteint 1 million d'unités.Babitang a lancé le premier clavier mécanique rétro
{{o.name}}
{{m.name}}

Je suppose que tu aimes

Origine my.oschina.net/u/4090830/blog/10093714
conseillé
Classement