写点什么

Spring 获取单例流程 (二)

用户头像
CoderLi
关注
发布于: 2020 年 06 月 15 日

读完这篇文章你将会收获到



  • ``Spring``prototype` 类型的 `bean`` 如何做循环依赖检测

  • ``Spring``singleton` 类型的 `bean`` 如何做循环依赖检测

前言

继上一篇文章 Spring 获取单例流程(一) 我们这次继续往下分析一下后面的流程





上一篇文章中我们说到,首先我们根据 ``name` 找到其对应的 `beanName` 、然后去缓存中看是否已经创建了/创建中这个对应的 `bean`,如果在缓存中找到了这个 `bean`、那么我们需要对这个 `bean` 可能进行一些处理,比如说用户想要的是一个普通的 `bean` 、但是在 `Spring` 缓存中找到的是一个 `factoryBean`、这个时候就要调用 `fatoryBean``getObject`` 方法以及对应的一些前置方法以及回调等。



那么如果我们在缓存中找不到这个 ``bean`` 那么流程又是怎么样?这个就是这篇文章要跟大家一起分享的

源码分析

if (sharedInstance != null && args == null) {
// 这里被我删除了一些spring 的log
// 处理一下 factory bean 的情况、包括从 factory beans 的缓存中获取、或者重新调用 factory bean 的 get bean 方法 包括一些回调
bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
} else {
// 从 上面的 getSingleton 拿不到对象的bean 、说明这个bean的scope 要么不是 singleton 要这个bean是singleton 但是没有初始化一句
// 因为 Spring 只解决单例模式下得循环依赖,在原型模式下如果存在循环依赖则会抛出异常
// 这里的循环依赖检查使用的 是 threadLocal 因为 prototype 类型的只是
if (isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
// 如果容器中没有找到,则从父类容器中加载
BeanFactory parentBeanFactory = getParentBeanFactory();
// parentBeanFactory 不为空且 beanDefinitionMap 中不存该 name 的 BeanDefinition
if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
// Not found -> check parent.
// 这里只是找出他的真正的beanName、并没有去掉 factory bean 的前缀
String nameToLookup = originalBeanName(name);
if (parentBeanFactory instanceof AbstractBeanFactory) {
return ((AbstractBeanFactory) parentBeanFactory).doGetBean(
nameToLookup, requiredType, args, typeCheckOnly);
} else if (args != null) {
// Delegation to parent with explicit args.
return (T) parentBeanFactory.getBean(nameToLookup, args);
} else if (requiredType != null) {
// No args -> delegate to standard getBean method.
return parentBeanFactory.getBean(nameToLookup, requiredType);
} else {
return (T) parentBeanFactory.getBean(nameToLookup);
}
}
.......
.......
........
}



第一步就是判断这个是否是一个 ``prototype` 类型的 `bean``,如果是并且正在创建中、那么就抛出一个循环依赖的异常



protected boolean isPrototypeCurrentlyInCreation(String beanName) {
// prototypesCurrentlyInCreation 是一个 threadLocal
Object curVal = this.prototypesCurrentlyInCreation.get();
return (curVal != null &&
(curVal.equals(beanName) || (curVal instanceof Set && ((Set<?>) curVal).contains(beanName))));
}



每一个 ``prototype``bean`` 创建的时候都会调用下面这个方法



protected void beforePrototypeCreation(String beanName) {
Object curVal = this.prototypesCurrentlyInCreation.get();
if (curVal == null) {
this.prototypesCurrentlyInCreation.set(beanName);
} else if (curVal instanceof String) {
Set<String> beanNameSet = new HashSet<>(2);
beanNameSet.add((String) curVal);
beanNameSet.add(beanName);
this.prototypesCurrentlyInCreation.set(beanNameSet);
} else {
Set<String> beanNameSet = (Set<String>) curVal;
beanNameSet.add(beanName);
}
}



``curVal` 要么是一个 `String` 要么是一个 `Set` , 而在创建 `prototype bean`` 完成之后



protected void afterPrototypeCreation(String beanName) {
Object curVal = this.prototypesCurrentlyInCreation.get();
if (curVal instanceof String) {
this.prototypesCurrentlyInCreation.remove();
} else if (curVal instanceof Set) {
Set<String> beanNameSet = (Set<String>) curVal;
beanNameSet.remove(beanName);
if (beanNameSet.isEmpty()) {
this.prototypesCurrentlyInCreation.remove();
}
}
}



可以看到 ``Spring` 使用 `ThreadLocal` 去做一个循环依赖的检测、我们在 `Spring` 资源加载的源码分析里面也提及到了、也是使用 `ThreadLocal`` 进行一个资源的循环引用的检测 Spring 容器的初始化



第二步则是判断当前的 ``beanFactory` 是否有父容器(父 `beanFactory`) ,如果有并且 `beanName``beanDefinition` 不存在当前的 `beanFactory` 中,那么则尝试在父容器中去获取这个 `bean``



我们继续往下看下面的代码



// 如果不是仅仅做类型检查则是创建bean,标记这个bean 已经创建了或者将要被创建
if (!typeCheckOnly) {
markBeanAsCreated(beanName);
}
try {
// 从容器中获取 beanName 相应的 GenericBeanDefinition,并将其转换为 RootBeanDefinition
final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
// 检查给定的合并的 BeanDefinition
checkMergedBeanDefinition(mbd, beanName, args);
// Guarantee initialization of beans that the current bean depends on.
// 处理所依赖的 bean
String[] dependsOn = mbd.getDependsOn();
if (dependsOn != null) {
for (String dep : dependsOn) {
// 如果是循环依赖
if (isDependent(beanName, dep)) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
}
// 注册
registerDependentBean(dep, beanName);
try {
// 看看我依赖的大佬好了没
getBean(dep);
} catch (NoSuchBeanDefinitionException ex) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"'" + beanName + "' depends on missing bean '" + dep + "'", ex);
}
}
}
......
......



第三步则是从 ``BeanDefinitionRegistry` 中获取注册的 `BeanDefinition` 继而获取这个 `bean` 所要依赖的其他 `bean` ,遍历其所依赖的 `bean`` 、判断是否循环依赖了



protected boolean isDependent(String beanName, String dependentBeanName) {
synchronized (this.dependentBeanMap) {
return isDependent(beanName, dependentBeanName, null);
}
}
private boolean isDependent(String beanName, String dependentBeanName, @Nullable Set<String> alreadySeen) {
if (alreadySeen != null && alreadySeen.contains(beanName)) {
return false;
}
String canonicalName = canonicalName(beanName);
// 找出依赖这个beanName的集合
Set<String> dependentBeans = this.dependentBeanMap.get(canonicalName);
// 没有人依赖这个beanName
if (dependentBeans == null) {
return false;
}
// 哦嚯、beanName 依赖的 bean、也依赖着beanName、完蛋
if (dependentBeans.contains(dependentBeanName)) {
return true;
}
// 看看依赖 beanName 的 其他依赖、有没有被dependentBeanName 依赖
// A 想依赖F、BCDE 依赖着A、那么我们现在来到这一步、已经确定了F不依赖A、那么我们要看看F是否依赖BCDE、如果依赖、那么就是循环依赖了
for (String transitiveDependency : dependentBeans) {
if (alreadySeen == null) {
alreadySeen = new HashSet<>();
}
alreadySeen.add(beanName);
if (isDependent(transitiveDependency, dependentBeanName, alreadySeen)) {
return true;
}
}
return false;
}



每一个 ``bean` 创建之前都会注册其依赖关系、主要由两个 `Map` 组成、一个是 `key` 为被依赖者,`value` 为依赖者集合,另一个则是 `key` 为依赖者,`value` 为被依赖者集合,比如说 `beanA` 依赖着 `beanB``beanC``



key 为被依赖者 value 为依赖者集合
beanB ---> beanA
beanC ---> beanA
key 为依赖者,value 为被依赖者集合
beanA ---> beanB,beanC



第四步则是去注册依赖关系,也就是往上面的两个 ``Map`` 中存放数据



public void registerDependentBean(String beanName, String dependentBeanName) {
String canonicalName = canonicalName(beanName);
// 在这个里面加上 这个依赖我的人
synchronized (this.dependentBeanMap) {
Set<String> dependentBeans =
this.dependentBeanMap.computeIfAbsent(canonicalName, k -> new LinkedHashSet<>(8));
if (!dependentBeans.add(dependentBeanName)) {
return;
}
}
// 在这里将我依赖的 那个大佬放进去我依赖的列表中
synchronized (this.dependenciesForBeanMap) {
Set<String> dependenciesForBean =
this.dependenciesForBeanMap.computeIfAbsent(dependentBeanName, k -> new LinkedHashSet<>(8));
dependenciesForBean.add(canonicalName);
}
}



最后的 ``getBean`` 则回到我们最初的起点



getBean(dep);



今天我们就先分析到这里、后续的话我们在后面的文章继续探讨。今天我们大致分析了





总结

  • 根据参数中的 ``name` 找出对应的 `beanName`、无论这个 `name` 是别名或者是一个 `factoryBean``beanName``

  • 查看缓存中是否包含这个 ``beanName`` 对象

- 先从一级缓存 ``singletonObjects`` 中看看有没有

- 然后从二级缓存 ``earlySingletonObjects``

- 都没有的话再从三级缓存 ``singletonFactories`` 中看看有没有

  • 如果缓存中有 ``bean`、那么我们还是需要处理一下这个 `bean``

- 如果 ``Spring` 缓存中返回的 `bean``factoryBean` 、而用户也想要的是一个 `beanFactory` (参数 `name` 中的前缀是 `&`` )、那么我们直接返回

- 如果 ``Spring` 缓存中返回的 `bean` 是普通的 `bean`、而用户也想要的是一个普通的 `bean`` 、那么就直接返回

- 如果 ``Spring` 缓存中返回的 `bean` 是一个 `factoryBean` 、而用户想要的是一个普通的 `bean` 、那么我们就要从 `factoryBean` 中获取这个 `bean``

- 而从 ``factoryBean` 中获取这个 `bean` 的过程中、需要调用到前置处理、后置处理和我们常用的接口回调 `BeanPostProcessor``

  • 如果缓存中没有 ``bean` 、则判断是否是 `prototype`` 类型并且循环依赖

  • 如果没有则尝试能否在父容器中找到该 ``bean``

  • 如果父容器也没有则获取该 ``beanName` 对应的 `beanDefinition` 找出其依赖的 `beanName``

  • 判断该 ``beanName` 与 依赖的 `beanName` 是否循环依赖、没有则注册其依赖关系并调用 `getBean` 方法去创建依赖的 `beanName``



发布于: 2020 年 06 月 15 日阅读数: 53
用户头像

CoderLi

关注

微信公众号:CoderLi ,专注于 Java 后端开发 2019.07.14 加入

还未添加个人简介

评论

发布
暂无评论
Spring 获取单例流程(二)