Spring 获取单例流程(二)
读完这篇文章你将会收获到
- 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


Spring 获取单例流程(二)的更多相关文章
- Spring 获取单例流程(三)
		读完这篇文章你将会收获到 Spring 何时将 bean 加入到第三级缓存和第一级缓存中 Spring 何时回调各种 Aware 接口.BeanPostProcessor .InitializingB ... 
- Spring 获取单例流程(一)
		读完这篇文章你将会收获到 在 getBean 方法中, Spring 处理别名以及 factoryBean 的 name Spring 如何从多级缓存中根据 beanName 获取 bean Spri ... 
- Spring IOC 容器源码分析 - 获取单例 bean
		1. 简介 为了写 Spring IOC 容器源码分析系列的文章,我特地写了一篇 Spring IOC 容器的导读文章.在导读一文中,我介绍了 Spring 的一些特性以及阅读 Spring 源码的一 ... 
- Spring源码分析(十五)获取单例
		本文结合<Spring源码深度解析>来分析Spring 5.0.6版本的源代码.若有描述错误之处,欢迎指正. 之前我们讲解了从缓存中获取单例的过程,那么,如果缓存中不存在已经加载的单例be ... 
- 5.2:缓存中获取单例bean
		5.2 缓存中获取单例bean 介绍过FactoryBean的用法后,我们就可以了解bean加载的过程了.前面已经提到过,单例在Spring的同一个容器内只会被创建一次,后续再获取bean直接从单例 ... 
- Spring源码分析(十三)缓存中获取单例bean
		摘要:本文结合<Spring源码深度解析>来分析Spring 5.0.6版本的源代码.若有描述错误之处,欢迎指正. 介绍过FactoryBean的用法后,我们就可以了解bean加载的过程了 ... 
- 【转】Spring Bean单例与线程安全
		一.Spring单例模式及线程安全 Spring框架中的Bean,或者说组件,获取实例的时候都是默认单例模式,这是在多线程开发的时候需要尤其注意的地方. 单例模式的意思是只有一个实例,例如在Sprin ... 
- Spring Bean单例与线程安全
		一.Spring单例模式及线程安全 Spring框架中的Bean,或者说组件,获取实例的时候都是默认单例模式,这是在多线程开发的时候需要尤其注意的地方. 单例模式的意思是只有一个实例,例如在Sprin ... 
- Spring的单例实现原理-登记式单例
		单例模式有饿汉模式.懒汉模式.静态内部类.枚举等方式实现,但由于以上模式的构造方法是私有的,不可继承,Spring为实现单例类可继承,使用的是单例注册表的方式(登记式单例). 什么是单例注册表呢, 登 ... 
随机推荐
- Java实现 LeetCode 388 文件的最长绝对路径
			388. 文件的最长绝对路径 假设我们以下述方式将我们的文件系统抽象成一个字符串: 字符串 "dir\n\tsubdir1\n\tsubdir2\n\t\tfile.ext" 表示 ... 
- java算法集训结果填空题练习1
			1 空瓶换汽水 浪费可耻,节约光荣.饮料店节日搞活动:不用付费,用3个某饮料的空瓶就可以换一瓶该饮料.刚好小明前两天买了2瓶该饮料喝完了,瓶子还在.他耍了个小聪明,向老板借了一个空瓶,凑成3个,换了一 ... 
- java实现不连续处断开
			不连续处断开 下列代码运行结果为: 12345 23456 89 23456789 即把一个串从数字不连续的位置断开.试完善之. String s = "123452345689234567 ... 
- java实现第六届蓝桥杯打印大X
			打印大X 打印大X 小明希望用星号拼凑,打印出一个大X,他要求能够控制笔画的宽度和整个字的高度. 为了便于比对空格,所有的空白位置都以句点符来代替. 要求输入两个整数m n,表示笔的宽度,X的高度.用 ... 
- Python数据分析之双色球高频数据统计
			Step1:基础数据准备(通过爬虫获取到),以下是从第一期03年双色球开奖号到今天的所有数据整理,截止目前一共2549期,balls.txt 文件内容如下 : 备注:想要现成数据的可以给我发邮件哟~ ... 
- java启动RabbitMQ消息报异常解决办法
			启动SpringCloud微服务,RabbitMQ报如下异常: 2019-08-12 18:15:49.543 ERROR 53096 --- [68.252.131:5672] o.s.a.r.c. ... 
- el-upload配合vue-cropper实现上传图片前裁剪
			需求背景 上传一个封面图,在上传之前需要对图片进行裁剪,上传裁剪之后的图片,类似微信的上传头像. 技术方案 上传肯定是用element的 el-upload 组件实现上传,非常方便,各种钩子函数. 裁 ... 
- Windows下虚拟机Linux(CentOS8)扩容设置 - 磁盘扩容中的坑和解决方法
			摘要:[原创]转载请注明作者Johnthegreat和本文链接 由于虚拟机空间不足,为了避免重装虚拟机,做了一次无损扩容. 过程中的报错如下: [root@localhost ~]# pvcrea ... 
- 58同城Java面试
			总结这一次面试失败的不冤 很多知识,都是了解.知道,而没有做到明白与彻底的弄懂 差距还是比较大的 以后要多来写总结,提升自己,争取早日被认可 说说今天面试的主要内容和问题吧 希望大家集思广益 面试职位 ... 
- is ==小数据池编码解码
			== 比较 比较的是两边的值 is 比较 比较的是内存地址 判断两个东西指向的是不是同一个对象 取内存地址 id() 小数据池 ... 
