C'est le 33ème jour de ma participation au "Nuggets Daily New Plan · August Update Challenge", cliquez pour voir les détails de l'événement
Basé sur Spring Framework v5.2.6.RELEASE
Suite de l'article précédent : Lecture du code source de Spring 20 : Obtenir le nom canonique du bean
résumer
L'article précédent a introduit la première étape d'obtention de l'objet d'instance de Bean, selon le name
nom canonique donné du Bean. Etant donné name
le nom d'alias du Bean ou le nom de backreference du FactoryBean, le nom canonique obtenu est l'identifiant unique du Bean dans le conteneur.
doGetBean
Cet article décrit ensuite la ligne de code clé suivante pour les méthodes de AbstractBeanFactory .
Object sharedInstance = getSingleton(beanName);
复制代码
Obtenir l'objet d'instance Bean à partir du cache
Accédez directement au getSingleton
code source de la méthode.
// org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String)
@Override
@Nullable
public Object getSingleton(String beanName) {
return getSingleton(beanName, true);
}
// org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean)
@Nullable
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;
}
复制代码
La fonction de cette méthode est d' beanName
obtenir l'objet d'instance Bean correspondant à partir du cache du conteneur. Il y a deux choses à noter ici :
- Le paramètre transmis en appelant la méthode est
transformedBeanName
le nom de bean canonique obtenu via la méthode. - En appelant la méthode surchargée, le
allowEarlyReference
paramètre reçoit une valeur par défauttrue
.
Ensuite, regardez le code dans le corps de la méthode. Tout d'abord, parcourez approximativement l'ensemble du processus du corps de la méthode. Vous pouvez constater qu'il y a trois collections dans le conteneur. Spring utilisera beanName pour trouver l'objet instance du bean à partir de ces trois Ces trois collections, on peut les considérer comme des caches (en fait des caches), le code pour les créer se trouve dans DefaultSingletonBeanRegistry :
/** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/** Cache of early singleton objects: bean name to bean instance. */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
/** Cache of singleton factories: bean name to ObjectFactory. */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
复制代码
Les clés de ces trois ensembles de cartes sont les noms des beans, et les valeurs sont les instances du bean singleton, les instances du bean singleton précédent et la fabrique de bean singleton, respectivement.
在getSingleton
方法的开头,会尝试从singletonObjects
集合获取 Bean 的实例,获取到就得到了想要的结果,直接返回。
当没有获取到时,如果isSingletonCurrentlyInCreation(beanName)
的结果为true
,那么尝试从下一个缓存 Map 中获取。isSingletonCurrentlyInCreation
方法的源码如下:
// org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#isSingletonCurrentlyInCreation
/**
* Return whether the specified singleton bean is currently in creation
* (within the entire factory).
* @param beanName the name of the bean
*/
public boolean isSingletonCurrentlyInCreation(String beanName) {
return this.singletonsCurrentlyInCreation.contains(beanName);
}
复制代码
这里还是一个集合,根据注释可以知道,这个方法的作用是判断一个单例 Bean 是不是正在被创建,也就是说这个集合中保存了所有正在被创建的 Bean 的名称。
返回到getSingleton
方法中,如果从singletonObjects
中获取不到,并且这个 Bean 正在被创建,那么就尝试从earlySingletonObjects
集合中获取。这里存放的是早期单例 Bean 的实例对象,这些 Bean 实例被创建好,但是还没有进行完初始化,就会被放在这个集合当中,可以提前被获取到了,如果这里获取到了目标 Bean 实例,那么也会作为方法的结果返回。
如果还没有获取到,并且allowEarlyReference
为true
,那么会接着尝试从singletonFactories
中获取,这里缓存的是创建早期单例的 Bean 工厂,如果从singletonFactories
集合中获取到了这个工厂对象,那么就调用它的getObject
方法,将早期 Bean 创建出来,并作为结果返回。
另外,如果早期 Bean 是在这里创建的,那么还需要把创建好的早期 Bean 添加到earlySingletonObjects
中,并把工厂从singletonFactories
移除掉,因为再次之后,它已经不会被用到了。
你可能会问了:何必呢?直接把 Bean 完整地初始化好放在一个缓存中获取,获取不到就创建新的,不就行了吗,为什么还需要早期实例和工厂实例这种中间状态呢?
这就要讲到循环依赖的问题。
循环依赖
假设接下来我们提到的类型,都是用来创建单例 Bean 的类型,思考这样三种情况:
- ClassA 中有一个属性的类型是 ClassA
- ClassA 中有一个属性的类型是 ClassB,同时,ClassB 中有一个属性的类型是 ClassA
- ClassA 中有一个属性的类型是 ClassB,ClassB 中有一个属性的类型是 ClassC,同时,ClassC 中有一个属性的类型是 ClassA
在之前的 Spring 源码阅读 19:如何 get 到一个 Bean? 一文中,曾经介绍过,完成一个 Bean 的初始化之前,必须要先初始化其各个属性值对应的 Bean。当出现以上三种情况或者类似的形成循环依赖关系的情况,就会出现问题。
以第 3 种情况为例,要完成 ClassA 的初始化,就要先初始化 ClassB,那么就要先初始化 ClassC,初始化 ClassC 又需要完成 ClassA 的初始化。这样就变成了「鸡生蛋蛋生鸡」的问题。
这种情况在实际的开发中,难以完全避免,因此,这是 Spring 必须要解决的问题。
三级缓存
Spring 解决这个问题的办法就是:在一个 Bean 还没有完成初始化的时候,就暴露它的引用。这里就需要用到前面介绍过的三个缓存 Map,也叫三级缓存。
以刚刚描述的情况为例,当 ClassA 需要需要一个 ClassB 的单例 Bean 作为属性填充的时候,先创建一个早期的 ClassB 的 Bean,此时,ClassB 刚刚被创建出来,还没有进行属性填充等初始化的流程,就将它放在earlySingletonObjects
中,这样,ClassA 的 Bean 实例就可以使用这个 ClassB 的早期实例进行属性填充,ClassB 的早期实例,可以再次之后再进行初始化,这样就不会因为循环依赖关系,导致无法初始化这三个 Bean。
暂时无法在飞书文档外展示此内容
La figure ci-dessus montre le processus d'initialisation de Bean et le processus d'obtention d'instances de Bean à partir de différents caches. Le processus détaillé sera analysé plus tard lorsque le processus d'obtention d'un Bean qui n'a pas été créé pour la première fois sera analysé en profondeur à travers le code.
limité
Cependant, la solution de Spring à ce problème a des limites.
Dans Spring, une instance de bean peut être injectée en tant que valeur de propriété dans une autre instance de bean via des méthodes de définition et des constructeurs. Selon le principe selon lequel Spring résout le problème de dépendance circulaire via le cache de troisième niveau, avant qu'un bean ne soit placé dans le cache, au moins l'objet doit être créé par réflexion. Cependant, si un attribut ne peut être injecté que via le constructeur, la création de l'objet Bean ne peut pas être terminée avant l'injection et le problème de la dépendance circulaire ne peut pas être résolu.
Par conséquent, Spring ne peut résoudre les dépendances circulaires que par injection de setter.
Résumer
Cet article analyse le principe selon lequel le conteneur Spring obtient des beans singleton à partir du cache, impliquant principalement le mécanisme de cache à trois niveaux conçu par Spring pour résoudre le problème de dépendance circulaire. Obtenez l'objet d'instance de bean à partir du cache via la getSingleton
méthode, il peut être obtenu, ou il peut ne pas l'être car le bean n'a pas encore commencé à être créé. Dans le prochain article, nous continuerons à analyser, si le bean singleton est obtenu ici, quel traitement doit être effectué ensuite.