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

  • 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 的 BeanDefinitionif (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 是一个 threadLocalObject 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,并将其转换为 RootBeanDefinitionfinal RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);//  检查给定的合并的 BeanDefinitioncheckMergedBeanDefinition(mbd, beanName, args);// Guarantee initialization of beans that the current bean depends on.// 处理所依赖的 beanString[] 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);// 没有人依赖这个beanNameif (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

http://weixin.qq.com/r/mSqvt_-ECrJ1ravU93_L (二维码自动识别)

spring el表达式 if else_Spring 获取单例流程(二)相关推荐

  1. spring 单例 获取多例的位_Spring 获取单例流程(一)

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

  2. Spring EL表达式使用详解

    Spring EL表达式使用详解 什么是Spring EL表达式 注入字面值 注入操作系统(OS)的属性 注入properties配置文件中数据 Bean属性调用 Bean方法调用 T运算符 构造器 ...

  3. Spring源码 - 从缓存中获取单例Bean

    # Spring源码 - 从缓存中获取单例Bean Spring版本:Spring 5.3.13-release # 1.从缓存中获取单例Bean 单实例Bean在Spring的同一个容器中只会创建一 ...

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

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

  5. 头条一面:Spring IOC容器中只存放单例Bean吗?

    最近,很多小伙伴出去面试,感觉自己面的不是很理想,回来后,不少小伙伴把面试题做了记录发给我,让我给大家解析下,然后发出来.当我看到这些面试题时,快速在脑海中构建起了整个知识体系,从基础到框架.从分布式 ...

  6. Spring 中的 bean 为什么默认单例?

    熟悉Spring开发的朋友都知道Spring提供了5种scope分别是singleton.prototype.request.session.global session. 如下图是官方文档上的截图, ...

  7. 面试官:为什么 Spring 中的 bean 默认为单例?

    作者 | 小小木 来源 | http://1t.click/ksQ 熟悉Spring开发的朋友都知道Spring提供了5种scope分别是singleton.prototype.request.ses ...

  8. Spring中的Bean默认是单例还是多例?如何保证并发安全?

    点击关注公众号,实用技术文章及时了解 Spring的bean默认都是单例的,某些情况下,单例是并发不安全的,以Controller举例,问题根源在于,我们可能会在Controller中定义成员变量,如 ...

  9. Spring EL表达式

    1,Spring EL 表达式简介 Spring EL 表达式是Spring提供的最新的灵活的注入方式,相比于传统的注解注入以及xml配置文件的注入远远地更加的强大. 2,Spring EL的功能 ( ...

最新文章

  1. 华为腾讯百度众安微众360大咖齐聚,2019中国区块链开发者大会首批议程曝光!...
  2. matlab求kcf算法响应图_剖析KCF
  3. 039_JavaScript对象访问器
  4. MATLAB可视化实战系列(四十二)-图像特征提取-使用低秩 SVD 进行图像压缩实例
  5. noi linux硬盘启动,NOI Linux + Windows 10双系统(Win10引导)安装记录
  6. 优化案例(part3)--Aberrance suppresse dspatio-temporal correlation filters for visual object tracking
  7. 彻底弄懂响应式设计中的em和rem
  8. 【招聘(北京)】北森测评招聘 .NET 架构师、高级工程师
  9. 【mysql】解决MySQL GPG密钥过期问题
  10. Package Control 使用
  11. 使用PyInstaller将Python程序打包成一个单独的exe文件
  12. Login控件在浏览器中打开时显示英文
  13. iOS 之 OBJECTC 调用C、OBJECTC调用C++
  14. 运维每天基本的工作流程
  15. 分享老齐【学方法】宽信用周期对股市的影响!
  16. Opencv:如何调用IP摄像头
  17. 电脑使用技巧提升篇5:Windows系统快捷键的使用
  18. 电费折扣充值cps接口
  19. matlab毕达哥拉斯质数,毕达哥拉斯质数
  20. Elixir服务器接收客户端消息01

热门文章

  1. 计算机主机中网卡的作用,计算机硬件组成及作用
  2. markdown 流程图_Markdown 进阶技能:用代码画流程图(编程零基础也适用)
  3. bigdecimal判断等于0_vue2.0源码用到的工具函数,12个简易的复用函数,看看有多简单...
  4. 光纤收发器在使用过程中有哪些需要注意的事项?
  5. 光纤收发器让网络布线变的更方便
  6. 光端机怎样使用?光端机怎么和交换机连接?
  7. 【渝粤教育】电大中专电商运营实操 (23)作业 题库
  8. 【渝粤题库】陕西师范大学200341实变函数作业(高起本、专升本)
  9. LoRa、Sigfox和NB-IoT在物联网趋势中谁是你的最佳拍档?
  10. linux编译安装的报错,linux编译安装时常见错误解决办法