spring 单例 获取多例的位_Spring 获取单例流程(一)
读完这篇文章你将会收获到
- 在
getBean
方法中,Spring
处理别名以及factoryBean
的name
Spring
如何从多级缓存中根据beanName
获取bean
Spring
如何处理用户获取普通bean
和factoryBean
引言
从 Spring 容器的初始化 中,我们了解到 Spring
是如何将 XML
文件转换为 BeanDefinition
并注册到 BeanDefinitionRegstry
今天我们一起继续学习 Spring
的 bean
加载
public static void main(String[] args) { Resource resource = new ClassPathResource("coderLi.xml"); DefaultListableBeanFactory defaultListableBeanFactory = new DefaultListableBeanFactory(); XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(defaultListableBeanFactory); xmlBeanDefinitionReader.loadBeanDefinitions(resource); }
<?xml version="1.0" encoding="UTF-8"?>beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "https://www.springframework.org/dtd/spring-beans-2.0.dtd"><beans> <bean class="com.demo.data.Person"> <description> 微信搜一搜:CoderLi description> bean>beans>
源码分析
我们可以在上面的 Java 代码中加入以下的代码
System.out.println(defaultListableBeanFactory.getBean("com.demo.data.Person#0"));
我们根据默认的 beanName 从 Spring 容器中获取 Person 这个 bean 对象,当然我们可以使用默认的别名获取
System.out.println(defaultListableBeanFactory.getBean("com.demo.data.Person"));
对 Spring 别名不熟悉的朋友可以先看下我的这一篇文章 Spring-AliasRegistry
我们直接进入到 AbstractBeanFactory#getBean(String)
方法中, AbstractBeanFactory
为 DefaultListableBeanFactory
的父类
@Overridepublic Object getBean(String name) throws BeansException { return doGetBean(name, null, null, false);}
可以看到 do
开头的才是真正干活的老大 , AbstractBeanFactory#doGetBean
protected T doGetBean(final String name, @Nullable final Class requiredType, @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {// 找到这个参数的 bean namefinal String beanName = transformedBeanName(name); Object bean;// Eagerly check singleton cache for manually registered singletons.// 检查缓冲中是否有这个bean、spring中只是保存单例的bean Object sharedInstance = getSingleton(beanName);if (sharedInstance != null && args == null) {// 这里被我删除了一些spring 的log// 处理一下 factory bean 的情况、包括从 factory beans 的缓存中获取、或者重新调用 factory bean 的 get bean 方法 包括一些回调 bean = getObjectForBeanInstance(sharedInstance, name, beanName, null); } .......... ...........
因为这个方法实在太长了所以截取一部分、我们一步步来分析
transformedBeanName(name)
这个方法就是将我们的 name 转换为真正的 beanName,因为我们传进来的参数可能是一个 alias 或者可能是一个 factoryBean 的 beanName (前缀为&),而我们在 Spring 中存放的 factoryBean 的 beanName 是没有 & 前缀的,所以需要处理掉这个前缀
protected String transformedBeanName(String name) { return canonicalName(BeanFactoryUtils.transformedBeanName(name));}public static String transformedBeanName(String name) { Assert.notNull(name, "'name' must not be null"); // 是否是一个 factory bean 、如果不是的话就直接返回 if (!name.startsWith(BeanFactory.FACTORY_BEAN_PREFIX)) { return name; } // 如果是的话就将其前缀 & 去掉 return transformedBeanNameCache.computeIfAbsent(name, beanName -> { do { beanName = beanName.substring(BeanFactory.FACTORY_BEAN_PREFIX.length()); } while (beanName.startsWith(BeanFactory.FACTORY_BEAN_PREFIX)); return beanName; }); } /** * 找到这个别名的最终的 bean Name、如果没有的话( * 也就是说参数中的name 就是人家的 bean name 那么就直接返回这个 参数就行了) * */ public String canonicalName(String name) { String canonicalName = name; // Handle aliasing... String resolvedName; do { resolvedName = this.aliasMap.get(canonicalName); if (resolvedName != null) { canonicalName = resolvedName; } } while (resolvedName != null); return canonicalName; }
让我们再看看下一个方法 DefaultSingletonBeanRegistry#getSingleton(String)
public Object getSingleton(String beanName) { // allowEarlyReference 允许早期依赖 return getSingleton(beanName, true);}
@Nullableprotected Object getSingleton(String beanName, boolean allowEarlyReference) {
Object singletonObject = this.singletonObjects.get(beanName); // 这个bean 正处于 创建阶段 if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { // 并发控制 synchronized (this.singletonObjects) { // 单例缓存是否存在 singletonObject = this.earlySingletonObjects.get(beanName); // 是否运行获取 bean factory 创建出的 bean if (singletonObject == null && allowEarlyReference) { // 获取缓存中的 ObjectFactory ObjectFactory> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { singletonObject = singletonFactory.getObject(); // 将对象缓存到 earlySingletonObject中 this.earlySingletonObjects.put(beanName, singletonObject); // 从工厂缓冲中移除 this.singletonFactories.remove(beanName); } } } } return singletonObject;}
上面的代码就是 Spring 尝试从缓存中加载单例。单例在 Spring 的同一个容器中只会被创建一次,后续再获取 bean,就直接从缓存中取了。
在介绍这个方法之前、我们先认识下 DefaultSingletonBeanRegistry
这个类里面的成员变量吧
Map singletonObjects
这个很好理解、 key 就是 beanName ,value 就是 bean 实例Map> singletonFactories
key 为 beanName,value 为创建 bean 的工厂Map earlySingletonObjects
key 为 beanName ,value 为 bean。但是和singletonObjects
不同的是,bean 被加入到earlySingletonObjects
的时候、这个 bean 还是处于一种创建中的状态,目的也很简单、Spring 用来解决某些场景下的循环依赖
我们再回到代码中、分析一下它的逻辑
- 先从 singletonObjects 中尝试获取 bean,这里存放的是已经创建好的 bean 了、如果在这里能知道、那当然是最好啦
- 如果在这里找不到的话、那么我们就要判断下这个 beanName 对应的 bean 是否正在创建中
- 如果是的话,那么我们再看看这个正在创建的 bean 是否已经曝光出来、如果没有的话、那么就要看看我们的参数是否允许依赖早期的 bean 了、
- 如果允许早期依赖、那么我们就尝试从 ObjectFactory 中获取到对应的 bean、并将它放入到 earlySingletonObjects 中、并从 singletonFactories 中移除
类似多级缓存的设计
在上面的方法中我们看到 isSingletonCurrentlyInCreation(beanName)
这个方法、
public boolean isSingletonCurrentlyInCreation(String beanName) { return this.singletonsCurrentlyInCreation.contains(beanName);}
singletonsCurrentlyInCreation
这个 Set 中,当创建一个 bean 之前会将其 对应的 beanName 放置到这个 Set 中、后面的分析会涉及到、这里先提一嘴
我们第一次获取这个 bean 、返回为 null 是正常的
那假如我们在代码中 getBean 了两次
defaultListableBeanFactory.getBean("com.demo.data.Person#0")defaultListableBeanFactory.getBean("com.demo.data.Person#0")
那么针对第二次的调用、返回的值就不是为 null 了
Object sharedInstance = getSingleton(beanName); if (sharedInstance != null && args == null) { // 处理一下 factory bean 的情况、包括从 factory beans 的缓存中获取、或者重新调用 factory bean 的 get bean 方法 包括一些回调 bean = getObjectForBeanInstance(sharedInstance, name, beanName, null); }
我们先假设 sharedInstance 不为 null 也就是我们第二次调用 getBean
,我们进入到 getObjectForBeanInstance
方法中
protected Object getObjectForBeanInstance( Object beanInstance, String name, String beanName, @Nullable RootBeanDefinition mbd) {
// 我王大锤就是想要一个 factory bean if (BeanFactoryUtils.isFactoryDereference(name)) {
// 如果这个是 NullBean 类型、表示这是一个 null 的 instance、直接返回 if (beanInstance instanceof NullBean) { return beanInstance; } // 获取到的 beanInstance 不是一个 factory、但是你tm name 又带有这个 & 很迷惑啊兄弟 if (!(beanInstance instanceof FactoryBean)) { throw new BeanIsNotAFactoryException(beanName, beanInstance.getClass()); }
if (mbd != null) { mbd.isFactoryBean = true; } return beanInstance; }
// 王大锤不想要factory bean、并且spring 也帮他找到了一个普通的 bean、直接返回 if (!(beanInstance instanceof FactoryBean)) { return beanInstance; }
// 王大锤要的是一个普通的bean 、但是spring 给他找到了一个 factory的bean、那么spring 是不是要做一些额外的处理 给王大锤返回一个普通的bean Object object = null;
if (mbd != null) { mbd.isFactoryBean = true; } else { // 从缓存中 看看有没有 object = getCachedObjectForFactoryBean(beanName); }
// 如果 bean factory 中还是没有 if (object == null) { // Return bean instance from factory. FactoryBean> factory = (FactoryBean>) beanInstance; // 从 缓存中拿到 bean definition if (mbd == null && containsBeanDefinition(beanName)) { mbd = getMergedLocalBeanDefinition(beanName); } // 是否是用户定义的还是程序本身需要创建的bean boolean synthetic = (mbd != null && mbd.isSynthetic());
object = getObjectFromFactoryBean(factory, beanName, !synthetic); } return object;}
我们按步骤分析下上面的代码
- 我们调用
getBean(name)
中的 name 如果包含前缀 & ,表面我们是想要从 Spring 中获取一个 FactoryBean ,那么我们就要判断我们从缓存中获取的 beanInstance 是否是 一个 FactoryBean 、如果是的话就直接返回不是的话就要抛出异常了 - 我们想要的是一个非 factoryBean 并且 在 spring 容器中找到了非 factoryBean 的 bean、那么就直接返回
- 我们想要的是一个 非 factoryBean 但是在 spring 容器中找到了一个 factoryBean 的 bean、那么就要进入到
getObjectFromFactoryBean
方法中了
protected Object getObjectFromFactoryBean(FactoryBean> factory, String beanName, boolean shouldPostProcess) { // 为单例模式且缓存中存在 if (factory.isSingleton() && containsSingleton(beanName)) {
synchronized (getSingletonMutex()) { // 从缓存中获取指定的 bean(这个bean 是从 factory bean 创建出来的) Object object = this.factoryBeanObjectCache.get(beanName);
if (object == null) { // 为空则从 factory bean 中获取对象 object = doGetObjectFromFactoryBean(factory, beanName); // 从缓存中获取 Object alreadyThere = this.factoryBeanObjectCache.get(beanName); if (alreadyThere != null) { // 已经存放到 缓存中了、后续的操作就不需要了 object = alreadyThere; } else { // 需要做一些后置处理 if (shouldPostProcess) { // 如果这个bean正在创建中、 if (isSingletonCurrentlyInCreation(beanName)) { return object; } // 前置处理 主要是将这个bean 加入到正在创建中的队列 singletonsCurrentlyInCreation beforeSingletonCreation(beanName); try { // 对 从 factoryBean 获取的对象进行后处理 // 生成对象将暴露给 bean 引用 并回调 beanPostProcessor object = postProcessObjectFromFactoryBean(object, beanName); } catch (Throwable ex) { throw new BeanCreationException(beanName, "Post-processing of FactoryBean's singleton object failed", ex); } finally { // 后置处理 将其从 singletonsCurrentlyInCreation 移除 afterSingletonCreation(beanName); } } // 他的 factory bean 已经存在 缓存中了、那么这个 factory bean 产生的bean 应该也要缓存一下 if (containsSingleton(beanName)) { this.factoryBeanObjectCache.put(beanName, object); } } }
return object; } } else { // 非单例 Object object = doGetObjectFromFactoryBean(factory, beanName);
if (shouldPostProcess) { try { // object = postProcessObjectFromFactoryBean(object, beanName); } catch (Throwable ex) { throw new BeanCreationException(beanName, "Post-processing of FactoryBean's object failed", ex); } } return object; }}
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊、代码很长长长..........我们一点点来分析
第一步就是判断这个 factoryBean 是否是单例、如果不是的话,并且是用户自己定义的 bean、那么就需要调用
postProcessObjectFromFactoryBean
方法去做一个后续的处理public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)throws BeansException { Object result = existingBean; for (BeanPostProcessor processor : getBeanPostProcessors()) { Object current = processor.postProcessAfterInitialization(result, beanName); if (current == null) { return result; } result = current; } return result;}
- 这里面最终回调的就是我们常用的一个接口
BeanPostProcessor
- 这里面最终回调的就是我们常用的一个接口
如果这 beanFactory 是一个单例,那我们就看看 factoryBeanObjectCache ( key 是 beanName,value 是 beanFactory 产生出来的 object 也是我们正要获取的 bean ) 这个 Map 中是否存在这个 beanName 这个 bean
如果存在的话、就直接返回、如果不存在的话、那就
doGetObjectFromFactoryBean
,从这个方法中使用 FactoryBean#getObject 产生 bean其实下面这段代码确实让人看不懂哦
Object alreadyThere = this.factoryBeanObjectCache.get(beanName);if (alreadyThere != null) {// 已经存放到 缓存中了、后续的操作就不需要了object = alreadyThere;}
然后我们看到
beforeSingletonCreation
这个方法、就是上面getSingleton
中isSingletonCurrentlyInCreation
判断一个 bean 是否处于正在创建中protected void beforeSingletonCreation(String beanName) { if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) { throw new BeanCurrentlyInCreationException(beanName); } } public boolean isSingletonCurrentlyInCreation(String beanName) { return this.singletonsCurrentlyInCreation.contains(beanName); }
然后又调用到
postProcessObjectFromFactoryBean
方法、最终回调的就是我们常用的一个接口BeanPostProcessor
最好调用
afterSingletonCreation(beanName)
方法、将其从 正在创建中的 bean 的集合中移除、最后的最后、将其加入到factoryBeanObjectCache
集合中
今天我们就先分析到这里、后续的话我们在后面的文章继续探讨。今天我们大致分析了 getBean 里面的这三个方法
总结
- 根据参数中的 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
- 如果 Spring 缓存中返回的 bean 是 factoryBean、而用户也想要的是一个 beanFactory (参数 name 中的前缀是
上面的三个方法大致流程就是这样、希望对各位有帮助
有兴趣进入群聊、一起交流一起划水
spring 单例 获取多例的位_Spring 获取单例流程(一)相关推荐
- php获取变量后几位,php获取变量类型_PHP成员变量获取对比
摘要 腾兴网为您分享:PHP成员变量获取对比,智宽生活,掌上看家,闲鱼,企业滴滴等软件知识,以及零钱包app,酷家乐3d云设计软件,桌面美化工具,湖北教育,lol盒子,摩擦,visualassistx ...
- spring 单例 获取多例的位_Spring系列第6篇:玩转bean scope,避免跳坑里!
公众号关注"程序员二哥", 设为'星标',带你学习更多的知识. 本文内容 详细介绍5中bean的sope及使用注意点 自定义作用域的实现 应用中,有时候我们需要一个对象在整个应用中 ...
- java 单例 读写锁_终极锁实战:单JVM锁+分布式锁
目录 1.前言 2.单JVM锁 3.分布式锁 4.总结 =========正文分割线================= 1.前言 锁就像一把钥匙,需要加锁的代码就像一个房间.出现互斥操作的典型场景:多 ...
- 单例模式不能被继承_Spring的单例实现原理
单例模式有饿汉模式.懒汉模式.静态内部类.枚举等方式实现,但由于以上模式的构造方法是私有的,不可继承,Spring为实现单例类可继承,使用的是单例注册表的方式. 什么是单例注册表呢: Spring是通 ...
- 单代号网络图计算例题_钣金展开计算210例节省放样场地,不理解公式也能完成展开计算!...
老路推荐<钣金展开计算210例>: 1.<钣金展开计算210例>介绍用计算的方法求得被展开构件各素线的实长,不用放大样,省去了放样场地. 2.全书共210个实例,用模板的形式编 ...
- 在c语言中以16位pc机为例一个,在C语言中(以16位pc机为例),一个char型数据在内...
话题:在C语言中(以16位pc机为例),一个char型数据在内存中所占字节回答:标准的C语言范中:char占一个字节int占4个字节话题:计算机中16位的数据如何存放在32位的寄存器中回答:如果是 w ...
- 使用 spring 的 IOC 解决程序耦合——获取spring的Ioc核心容器,并根据id获取对象、核心容器的两个接口(ApplicationContext、BeanFactory)引发出的问题
IOC概念和spring中的IOC 明确 ioc 的作用: 削减计算机程序的耦合(解除我们代码中的依赖关系). 使用 spring 的 IOC 解决程序耦合 获取spring的Ioc核心容器,并根据i ...
- python 获取 精确的13位时间戳 以及使用datetime 获取时间间隔
调度任务中常常要用到精确的时间,如果获取的13位时间戳不够准确的话,会造成数据的重复或者缺失 通常的不准确的13位时间戳的获取方式(秒*1000) import time millis = int(r ...
- php获取后台数据并输出,html表单通过关联数组向php后台传多条数据并遍历输出...
通过表单向php后台传多条数据,以关联数组方式呈现,废话不多说,代码附上: html表单代码,方式我设置为get: 数据1 数据2 php中的代码: $arr=$_GET['cName']; fore ...
最新文章
- 在ASP.Net2.0中使用UrlRewritingNet实现链接重写
- Will Wright总是能给我们带来惊奇啊
- 将RGB值转换为灰度值的简单算法(转)
- 阶段2 JavaWeb+黑马旅游网_15-Maven基础_第2节 maven的安装和仓库种类_05仓库的种类和彼此关系...
- 03-字典的增删改查
- 修改Apache配置文件开启gzip压缩传输
- 南昌航空大学961数据结构真题答案
- verilog 语法笔记
- 2017年国家公务员考试行测错题集(省级)
- json-lib将xml转json报错java.lang.NoClassDefFoundError: nu/xom/ParentNode
- Linux驱动之TTY(一):概念
- error:尝试引用已删除的函数或已显式删除函数
- mysql按照年龄区间分组查询
- git将某远程分支的某次提交合并到另一远程分支
- 基于xc7k325t fbg900的IBRET的测试流程
- 防止衣物褪色的小妙招
- 侍魂服务器维护补偿,侍魂胧月传说:套路!花了15万元玩强化类氪金游戏总结出来的经验...
- 在线语音转文字工具V1.0
- 国科大学习资料--最优化计算方法(王晓)-期末考试真题3
- 智慧交管大屏可视化决策系统
热门文章
- SC-A-LOAM:在A-LOAM中加入回环检测
- 进一步封装axios并调用其读取数据(吐槽~在安卓9.0以下或者IOS10.X以下手机端H5页面不支持,在这两种情况下的系统只能使用ajax或者原生js请求后台数据)
- Ubuntu 14.04 64bit上安装Intel官方集显更新驱动程序
- 数据库1.0 -- 数据库的基本操作
- CarTool 使用,获取图片资源
- Linux命令行与shell脚本编程大全:第2版
- Python笔记:字典的fromkeys方法创建的初始value同内存地址问题
- win10安装docker并结合Idea2018.1部署springboot项目
- Python学习(一) 安装,环境搭建,IDE
- mac 下周期调度命令或脚本