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

  • Springprototype 类型的 bean 如何做循环依赖检测
  • Springsingleton 类型的 bean 如何做循环依赖检测

前言

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

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

那么如果我们在缓存中找不到这个 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))));
}

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

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) ,如果有并且 beanNamebeanDefinition 不存在当前的 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 依赖着 beanBbeanC

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 是别名或者是一个 factoryBeanbeanName
  • 查看缓存中是否包含这个 beanName 对象
    • 先从一级缓存 singletonObjects 中看看有没有
    • 然后从二级缓存 earlySingletonObjects
    • 都没有的话再从三级缓存 singletonFactories 中看看有没有
  • 如果缓存中有 bean、那么我们还是需要处理一下这个 bean
    • 如果 Spring 缓存中返回的 beanfactoryBean 、而用户也想要的是一个 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

Spring 获取单例流程(二)的更多相关文章

  1. Spring 获取单例流程(三)

    读完这篇文章你将会收获到 Spring 何时将 bean 加入到第三级缓存和第一级缓存中 Spring 何时回调各种 Aware 接口.BeanPostProcessor .InitializingB ...

  2. Spring 获取单例流程(一)

    读完这篇文章你将会收获到 在 getBean 方法中, Spring 处理别名以及 factoryBean 的 name Spring 如何从多级缓存中根据 beanName 获取 bean Spri ...

  3. Spring IOC 容器源码分析 - 获取单例 bean

    1. 简介 为了写 Spring IOC 容器源码分析系列的文章,我特地写了一篇 Spring IOC 容器的导读文章.在导读一文中,我介绍了 Spring 的一些特性以及阅读 Spring 源码的一 ...

  4. Spring源码分析(十五)获取单例

    本文结合<Spring源码深度解析>来分析Spring 5.0.6版本的源代码.若有描述错误之处,欢迎指正. 之前我们讲解了从缓存中获取单例的过程,那么,如果缓存中不存在已经加载的单例be ...

  5. 5.2:缓存中获取单例bean

    5.2  缓存中获取单例bean 介绍过FactoryBean的用法后,我们就可以了解bean加载的过程了.前面已经提到过,单例在Spring的同一个容器内只会被创建一次,后续再获取bean直接从单例 ...

  6. Spring源码分析(十三)缓存中获取单例bean

    摘要:本文结合<Spring源码深度解析>来分析Spring 5.0.6版本的源代码.若有描述错误之处,欢迎指正. 介绍过FactoryBean的用法后,我们就可以了解bean加载的过程了 ...

  7. 【转】Spring Bean单例与线程安全

    一.Spring单例模式及线程安全 Spring框架中的Bean,或者说组件,获取实例的时候都是默认单例模式,这是在多线程开发的时候需要尤其注意的地方. 单例模式的意思是只有一个实例,例如在Sprin ...

  8. Spring Bean单例与线程安全

    一.Spring单例模式及线程安全 Spring框架中的Bean,或者说组件,获取实例的时候都是默认单例模式,这是在多线程开发的时候需要尤其注意的地方. 单例模式的意思是只有一个实例,例如在Sprin ...

  9. Spring的单例实现原理-登记式单例

    单例模式有饿汉模式.懒汉模式.静态内部类.枚举等方式实现,但由于以上模式的构造方法是私有的,不可继承,Spring为实现单例类可继承,使用的是单例注册表的方式(登记式单例). 什么是单例注册表呢, 登 ...

随机推荐

  1. Java实现 LeetCode 599 两个列表的最小索引总和(使用hash提高效率)

    599. 两个列表的最小索引总和 假设Andy和Doris想在晚餐时选择一家餐厅,并且他们都有一个表示最喜爱餐厅的列表,每个餐厅的名字用字符串表示. 你需要帮助他们用最少的索引和找出他们共同喜爱的餐厅 ...

  2. Java实现 LeetCode 232 用栈实现队列

    232. 用栈实现队列 使用栈实现队列的下列操作: push(x) – 将一个元素放入队列的尾部. pop() – 从队列首部移除元素. peek() – 返回队列首部的元素. empty() – 返 ...

  3. Java实现 LeetCode 213 打家劫舍 II(二)

    213. 打家劫舍 II 你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金.这个地方所有的房屋都围成一圈,这意味着第一个房屋和最后一个房屋是紧挨着的.同时,相邻的房屋装有相互连通的防盗 ...

  4. Java实现 蓝桥杯VIP 算法提高 欧拉函数

    算法提高 欧拉函数 时间限制:1.0s 内存限制:512.0MB 说明 2016.4.5 已更新试题,请重新提交自己的程序. 问题描述 给定一个大于1,不超过2000000的正整数n,输出欧拉函数,p ...

  5. Java实现 LeetCode 78 子集

    78. 子集 给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集). 说明:解集不能包含重复的子集. 示例: 输入: nums = [1,2,3] 输出: [ [3], [1], ...

  6. Java实现 蓝桥杯 乘积最大

    输入输出样例 输入样例#1: 4 2 1231 输出样例#1: 62 import java.util.Scanner; public class chengjizuida { public stat ...

  7. chattr +i 用户也没法随意删除

    root用户也没法用rm随意删除文件?   前言 在你的印象中,是不是root用户就可以为所欲为呢?随便一个rm -rf *,一波骚操作走人?可能没那么容易. 先来个示例,创建一个文本文件test.t ...

  8. ZWave 数据包格式

    ZWave Device 的过程中,其实就是对不同的 COMMAND CLASS 进行解析处理.在刚开始进入ZWAVE 开发时,为了弄清楚数据包的格式,做了下面的参考表格.不过后来发现用来抓包的 sn ...

  9. Docker中使用ElasticSearch

    安装Docker yum install docker //安装完成以后,可以查看一下docker的版本 docker -v //Docker version 1.13.1, build 64e998 ...

  10. Beta 冲刺

    这个作业属于哪个课程 软件工程 (福州大学至诚学院 - 计算机工程系) 这个作业要求在哪里 Beta 冲刺 这个作业的目标 Beta 冲刺 汇总博客 作业正文 如下 其他参考文献 ... 说明:此博客 ...