目录

一句话概括

本文以走马观花的姿态,简单分析了一波 spring 容器创建bean的大致环节,后续将通过单独的文章进行细讲其中的没个环节。

本文将包含后文的链接,根据感兴趣的内容自取即可。

1 书接上回

我们已经知道了spring 是怎么解析标签的。

现在我们解析完标签并注册到 BeanFactoryRegistry 接口进行管理了,接下来我们要用解析的结果创建bean了

记得开篇说的那两行代码么:

import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource; public class XmlBeanFactoryTest {
BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("bean.xml"));
Object object = beanFactory.getBean("action");
}

现在正式进入第二行代码的流程:

Object object = beanFactory.getBean("action");

同理 【Shift + Ctrl + 鼠标左键】 尝试找到 getBean 的实现类,这里发现有多个符合条件的实现类;

再解析 XmlBeanFactory 的类图,那么显而易见,我们应该看 AbstractBeanFactory 类的 getBean() 方法

然后再层层递进,我们来到了 AbstractBeanFactory.doGetBean() 方法,这个方法的方法体非常长,如果不考虑其中细节,我们只需要讲解这一个方法大致就可以知道 BeanFactory.getBean() 最基本的实现,及其重要环节。

2 揭开 doGetBean() 的神秘面纱, 重要操作一览

实际上在spring 的5.x版本中, doGetBean()方法体非常的长, 应该在 150 行左右了,或许屏幕前的你会嗤之以鼻,就这?

如果平时有严格讲究clean code 规范,那么 超过 50 行的方法都是不合理的。(刚毕业时在某家公司见过 2700 多行的方法,虽然离职许久了,但是我敢肯定时至今日,那些代码必定还在"成长"。)

下边我会贴一串处理后的伪代码,主要用来展示 doGetGean() 方法的骨架:


protected <T> T doGetBean(
String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
throws BeansException {
// 1. 转换/提取beanName name 可能为:FactoryBean 、 别名alias 等 String beanName = transformedBeanName(name);
Object bean; // 2. 尝试直接从缓存中获取,或者从 singletonFactories 中的 ObjectFactory 获取 // - 检查缓存中或者实例工厂中是否有对应实例
Object sharedInstance = getSingleton(beanName); // 第一次尝试从单例bean池中获取缓存的 bean 实例
if (sharedInstance != null && args == null) {
// && 2.1 如果 bean 原型是FactoryBean 类型,则通过其 getObject() 方法生成真正的 bean
// && 2.2 否则直接返回 sharedInstance 本身作为: 待获取的bean
bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
} else { // 3. 缓存中不存在,那么就要从头再来了, 从前文 xml 标签解析到的 BeanDefinition 开始,逐步生成真正的 bean 对象
// 3.1 存在无法消解的循环引用
if (isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
} // 3.2 parentBeanFactory 检查
// parentBeanFactory 不为空 && 当前加载的XML中不包含 beanName 映射的类时,会尝试从 parentBeanFactory 获取
BeanFactory parentBeanFactory = getParentBeanFactory();
if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
// 3.2.1 当前工厂没能获取到相关 bean 向 parentBeanFactory 询问
String nameToLookup = originalBeanName(name);
if (parentBeanFactory instanceof AbstractBeanFactory) {
return ((AbstractBeanFactory) parentBeanFactory).doGetBean(nameToLookup, requiredType, args, typeCheckOnly);
} else if (args != null) {
return (T) parentBeanFactory.getBean(nameToLookup, args);
} else {
return parentBeanFactory.getBean(nameToLookup, requiredType);
}
} // 4. 暂停一下, 能走到这一步说明:
// 4.1 第一,从缓存取不到,那么是第一次加载当前bean;
// 4.2 第二,没有进入上述的 parentBeanFactory 的流程里,说明 当前 的BeanFactory 里有带获取bean 的定义。 // 5 类比 * 提前暴露,如果该配置生效,通过检查则,视为该 bean 已经创建
if (!typeCheckOnly) {
// 标记该 bean 已经被创建过
markBeanAsCreated(beanName);
} // 6. 正式进入,从零开始创建一个 bean 的过程
try {
// 6.1 对 bean 的后续处理都是针对 RootBeanDefinition 进行的,所以需要:
// 将存储的 XML 配置文件的 GenericBeanDefinition 转化为 RootBeanDefinition --> ChildBeanDefinition
RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName); // 老熟人了,第一个系列的文章讲的就是怎么得到她 // 6.2 校验 mbd 对应的类是否为抽象类,[ 当前代码版本: spring 5.1 ]
checkMergedBeanDefinition(mbd, beanName, args); // Guarantee initialization of beans that the current bean depends on.
// 6.3 获取引用关系, 实例化依赖的 bean [需要spring容器注入的依赖]
String[] dependsOn = mbd.getDependsOn();
if (dependsOn != null) {
for (String dep : dependsOn) {
if (isDependent(beanName, dep)) { // 无法处理的, 循环依赖抛异常 TODO
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
}
registerDependentBean(dep, beanName); // 记录bean的name 记录他们之间的引用关系,被销毁时根据关系去销毁
try {
getBean(dep); // 递归创建当前bean,依赖的其它 bean
} catch (NoSuchBeanDefinitionException ex) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"'" + beanName + "' depends on missing bean '" + dep + "'", ex);
}
}
} // 7. 创建bean自身
// 它依赖的 bean [ 加载/创建 ] 完毕, 最终到了加载其本身的时刻 (根据它的 beanDefinition 创建真正的 bean 实例)
if (mbd.isSingleton()) {
// 7.1 单例模式
// 第二次尝试从单例bean缓存池中获取该bean实例;若无法从缓存中获取(未被加载),从头开始加载该 bean 的实例
// 具体加载行为由 createBean()方法负责
// 回调 getObject().createBean()前: 实例化前的准备工作 -- before[PostProcessor(后置处理器)]
// 回调 getObject().createBean()后: 实例化完后的补充工作 -- after[PostProcessor(后置处理器)]
sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
public Object getObject() throws BeansException {
try {
// 准备完成后回调 由子类 AbstractAutowireCapableBeanFactory 实现方法
return createBean(beanName, mbd, args);
} catch (BeansException ex) {
// 出错,单例工厂销毁该 bean
destroySingleton(beanName);
}
}
});
// 返回对应的实例 有些情况并不直接返回 实例本身,而是返回 <指定方法> <返回的实例>
// <指定方法> : 实现 《特定工厂接口》 的 《工厂实例》 的某个方法
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
} else if (mbd.isPrototype()) {
// 7.2 prototype 原型模式, 实例化的手段: 直接创建新的bean
Object prototypeInstance = null;
try {
// 7.2.1 标记 bean 正在被创建
beforePrototypeCreation(beanName);
// 7.2.2 进入创建流程
prototypeInstance = createBean(beanName, mbd, args);
} finally {
// 7.2.3 标记的取消: 正在创建 (7.2.1 的逆向操作)
afterPrototypeCreation(beanName);
}
// 返回对应的bean实例,如果是 FactoryBean 时, 返回其 getObject() 方法的返回值 TODO
bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
} else {
// 根据配置的 scope 实例化 bean (除单例-singleton、原型-prototype 之外)
String scopeName = mbd.getScope();
Scope scope = this.scopes.get(scopeName);
try {
Object scopedInstance = scope.get(beanName, () -> {
beforePrototypeCreation(beanName);
try {
return createBean(beanName, mbd, args);
} finally {
afterPrototypeCreation(beanName);
}
});
// 返回对应的实例 有些情况并不直接返回 实例本身,而是返回 <指定方法> <返回的实例>
bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
} catch (IllegalStateException ex) { }
}
} catch (BeansException ex) {
// 发生异常后,清理现场
cleanupAfterBeanCreationFailure(beanName);
}
} // 检查生成的bean类型是否符合实际需求
if (requiredType != null && !requiredType.isInstance(bean)) {
try {
T convertedBean = getTypeConverter().convertIfNecessary(bean, requiredType);
return convertedBean;
} catch (TypeMismatchException ex) {
throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
}
}
return (T) bean;
}

关键词:

createBean(beanName, mbd, args);

文中一共三处,你可以仔细观察这三处 createBean() 所处的语境:

  • 第一次是 单例的场景, createBean() 方法的返回值,被一个单例处理的方法接收。它会根据 bean 的名称判断是否已经实例化过了,这里为了保证单例,甚至还用了双重锁机制。

  • 第二次是原型模式, 也就是创建对象时直接new, 你看这里 createBean() 的上下文语境,没有被任何的校验操作环绕,直接就返回了。

  • 第三次出现,是为了实例化上述两种类型之外的 bean, 这里 createBean() 被 Scope.get() 方法接收,可以确定的是这里必定会发生,根据 bean的Scope作用域而进行的校验。

没错上边展示的就是 "骨架", 往简单了说,getBean() 所做的事情不外乎上边提到的内容。

但是实际上呢,createBean() 的过程中还有许许多多重要的操作需要一一道来,后续章节将围绕上述的 "骨架" 进行展开。

后边的章节可以看作是一个目录,如果内容比较多,我会通过一个外部链接单独展示相关的内容。若篇幅较为短小,则会直接在下边呈现。

3 必要的课外知识

3.1 bean 的作用域 Scope 有哪些

2、bean 的作用域 Scope 有哪些

3.2 FactoryBean 是什么

3、FactoryBean的使用

3.3 什么是循环依赖

4、循环依赖及其消解

4 深入了解 doGetBean

第三节介绍的内容将服务于本节接下来的内容,接下来我们将回顾 doGetBean 的整个流程,然后对如下列出的环节进行或详细或粗略的介绍。

1) 深入了解 doGetBean() 之 尝试从缓存 [单例] 读取bean

5、单例bean的获取

2) bean 与 FactoryBean

源码中找到如下这行代码:

假如上一步中,我们从缓存中读取到了单例bean, 本小节将延续这条路线。

前边讲 FactoryBean 的时候就提到它了.

这里进去做的事就比较好理解了:

1 判断 bean 类型:如果不是 FactoryBean,或者bean命名不符合 FactoryBean 的命名格式,方法返回

2 若是 FactoryBean,先尝试从缓存获取( 缓存中保存的是:FactoryBean.getObject() 生成好的bean );从缓存获取失败后进入后续流程

3 判断是否单例,如果是单例场景又是一套组合拳:锁定 singletonObjects 容器,然后进入从FactoryBean调用 getObject() 获取bean;

getObject() 后再次尝试读取缓存,若获取到了则舍弃上述读取动作的新的bean,以缓存中的为旧bean作为最终结果;而后进入bean的后处理器的增强操作流程;

而后方法返回前将加载结果放到单例缓存容器: factoryBeanObjectCache 中。需要注意的是:容器 factoryBeanObjectCache 仅仅为单例场景服务,因为 "多例" 场景下根本不需要缓存,每次直接从 FactoryBean 获取全新的 bean即可。

【其实这里也回答了,介绍 FactoryBean那篇文章中末尾遗留的问题:我们并不需要在 FactoryBean 的内部实现单例,spring 容器已经帮我们实现了,FactoryBean的单例管理。 】

4 若不是单例,无需考虑全局唯一,直接从 FactoryBeab 获取全新 bean;接着判断是否需要后置处理器的增强.... 至此流程结束。。。。。

实际上到目前位置的逻辑处理的都是,从缓存中成功获取到了bean的流程。

后续章节中,将围绕从 0 开始创建bean 的流程展开。

3) 深入了解 doGetBean() 之 ParentBeanFactory

这里逻辑其实挺简单的:

  • 1 标记的第一处:其实就是判断 bean 是否已经进入创建流程,isPrototypeCurrentlyInCreation 内部其实就是一个 ThreadLocal 容器,如果你想深入追踪它,

    你可以关注这个 ThreadLocal 容器的set() 方法的调用链路,其实不难发现它的set 方法的调用,同样是在 doGetBean() 的后续环节中。

    这里如果我们讨论的是一个全新bean的创建,那么肯定就不存在冲突。

  • 2 标记的第二处: 尝试获取当前 BeanFactory 的 "父工厂", 然后判断当前 BeanFactory 的 BeanDefinitionRegistry 是否有被请求bean 的定义;

    如果当前BeanFactory 中不包含该 bean的定义,且 "父工厂" 不为空时,bean 的加载动作将被转嫁给 "父工厂" 去执行。

很明显,这里用 beanName 调用了 parentBeanFactory 的 getBean() 、 doGetBean() 等方法,这里就可以视作套娃递归了。

当然如果符合上述的情形,我们当前的分析就到头。

如果时不走 “父工厂” 的情形,我们接着往下看。

4) 深入了解 doGetBean() 之 BeanDefinition 对象的处理

BeanDefinition: 如果你了解过 spring 对 xml 文件的加载,我想你对它不会陌生吧。实际上 spring 对xml 文件解析的结果,就是以 BeanDefinition 的形式进行保存的。

如果你还没忘记,我们当前仍然在 XmlBeanFactory().getBean() 的流程里。我们的大前提是:通过前边章节的介绍,XmlBeanFactory 已经完成了 对 xml 配置文件的解析,并通过 BeanDefinitionRegistry 接口进行管理。

如果忘了,我们再把 XmlBeanFactory 的类图放出来瞅瞅:

接着看源码:

这几行代码也比较干练,这里做的几件事无非就是根据 beanName,从BeanDefinitionRegistry 接口读取, 相应的 BeanDefinition,并进行一定的转化:

  • 1 如果从 BeanDefinition 中发现,它存在父类,那么会去读取父类,最终并用子类属性去覆盖父类的 BeanDefinition, 得到该bean的最终定义。
  • 2 对于上述装换操作完成的bean, 还可以通过容器缓存起来,若该bean 再次被加载时就可以重复利用了。( "多例"bean 很容易被多次加载)
  • 3 校验该 beanName 指向的是否是抽象类,如果是,抛出异常
  • 4 从 BeanDefinition 获取,它依赖的别的 bean, 如果有依赖的bean,那么就转向被依赖的bean的加载,上图倒数第四行,承接的就是该操作;

    事实上bean加载时,都是要先去加载其依赖的bean的,它会递归的去加载所有被依赖的 bean,直至所有bean都被加载为止。

    【其实前边讲循环依赖,时提到过这个概念的。】

如果当前bean 依赖了别的bean,那么没什么好说的,这里就转向被依赖bean的加载。

如果被依赖bean全部被加载完了,或者它就不依赖任何的其它 bean,那么我们将进入接下来的流程。

5) 不同作用域(Scope) bean 的加载动作

如下的缩略图所示,就是本文剩下的全部内容了,这里分为对:单例bean、原型(“多例”)bean、其它bean的加载,实际上我们接触最多的就是单例bean的加载。

其实这里虽然根据不同作用域分为了3种情形,实际进行bean加载的动作其实大同小异,主要的差异在体现在作用域的特性上:

  • 单例:它关注的bean缓存的操作,保证全局唯一
  • 原型(多例): 它不需要保证唯一,每次直接创建全新bean,所以 spring 中对多例 bean的加载逻辑,反而比单例bean的加载简单了太多太多。

6) 深入了解 doGetBean() 之 《如何从零开始创建bean》

本文篇幅将会拉长,故通过单独的文章呈现。

6、从零开始的 bean 创建.md

7) 深入了解 doGetBean() 之: 从零开始的单例bean创建

8、单例bean的创建

8) 深入了解 doGetBean() 之 原型(多例) bean 的加载

从零开始的 多例bean 创建没啥好说的,因为每次必定创建全新的bean,那么就不需要设置缓存、全局唯一性检查等操作了,

所以多例 bean的 创建逻辑反而就只剩下了:

  • createBean() + getObjectForBeanInstance()

9) 深入了解 doGetBean() 之: 其它作用域的bean创建

看这个代码结构,是不是很眼熟?跟单例bean的创建代码大同小异。

那么我们可以推测,当需要使用其它作用域时,那么需要向 BeanFactory 注册相关 "作用域的解析器",也是上述截图中的:

  • scopes

对应作用域解析器,根据作用特性开发满足条件的 Scope.get(String beanName, ObjectFactory objFactory) 即可。

比如我们前边提到的单例作用域bean,就一个目的: 全局唯一。

如果忘了 bean 还有哪些作用域,可以回头去复习一下。

10) 深入了解 doGetBean() 之 生成的 bean类型检查

这里也很简单,getBean 支持指定 bean的类型,当bean 的加载结束后,如果参数指定了: requiredType

那么就需要进行参数类型校验。

5 系列二 getBean() 总结

这里,除了单纯的bean实例化、初始化,还包含了很多其它的的知识:

  • spring bean 的作用域及其特性,以及spring 面临相关作用域bean的加载时所做的动作;

  • FactoryBean 和 ObjectFactory

  • 循环依赖

  • 后置处理器

... 等等

别看仅一个 getBean() , 包含的东西实在太多太多了。

6 后续展望

实际上到了这里我们对 spring 基础功能的学习还是有所欠缺,比如大名鼎鼎的:AOP

毕竟 IOC 和 AOP 是 spring 的两大基本特性么。

不过好消息是对于AOP 的学习可以继承到目前为止我们的学习成果。

  • 实际上简单来说 AOP 干的事,就是普通bean 被加载成功后,再应用动态代理技术生成bean的子类,

    并把通过AOP定义增强内容实现。

《系列二》-- 1、BeanFactory.getBean 总览的更多相关文章

  1. swoft| 源码解读系列二: 启动阶段, swoft 都干了些啥?

    date: 2018-8-01 14:22:17title: swoft| 源码解读系列二: 启动阶段, swoft 都干了些啥?description: 阅读 sowft 框架源码, 了解 sowf ...

  2. 【转载】PyTorch系列 (二):pytorch数据读取

    原文:https://likewind.top/2019/02/01/Pytorch-dataprocess/ Pytorch系列: PyTorch系列(一) - PyTorch使用总览 PyTorc ...

  3. 使用 PySide2 开发 Maya 插件系列二:继承 uic 转换出来的 py 文件中的类 Ui_Form

    使用 PySide2 开发 Maya 插件系列二:继承 uic 转换出来的 py 文件中的类 Ui_Form 开发环境: Wing IDE 6.1 步骤1: 打开 Wing IDE,创建一个新的 pr ...

  4. 前端构建大法 Gulp 系列 (二):为什么选择gulp

    系列目录 前端构建大法 Gulp 系列 (一):为什么需要前端构建 前端构建大法 Gulp 系列 (二):为什么选择gulp 前端构建大法 Gulp 系列 (三):gulp的4个API 让你成为gul ...

  5. WPF入门教程系列二十三——DataGrid示例(三)

    DataGrid的选择模式 默认情况下,DataGrid 的选择模式为“全行选择”,并且可以同时选择多行(如下图所示),我们可以通过SelectionMode 和SelectionUnit 属性来修改 ...

  6. Web 开发人员和设计师必读文章推荐【系列二十九】

    <Web 前端开发精华文章推荐>2014年第8期(总第29期)和大家见面了.梦想天空博客关注 前端开发 技术,分享各类能够提升网站用户体验的优秀 jQuery 插件,展示前沿的 HTML5 ...

  7. Web 前端开发人员和设计师必读文章推荐【系列二十八】

    <Web 前端开发精华文章推荐>2014年第7期(总第28期)和大家见面了.梦想天空博客关注 前端开发 技术,分享各类能够提升网站用户体验的优秀 jQuery 插件,展示前沿的 HTML5 ...

  8. Web 开发精华文章集锦(jQuery、HTML5、CSS3)【系列二十七】

    <Web 前端开发精华文章推荐>2014年第6期(总第27期)和大家见面了.梦想天空博客关注 前端开发 技术,分享各类能够提升网站用户体验的优秀 jQuery 插件,展示前沿的 HTML5 ...

  9. Web 前端开发人员和设计师必读精华文章【系列二十六】

    <Web 前端开发精华文章推荐>2014年第5期(总第26期)和大家见面了.梦想天空博客关注 前端开发 技术,分享各类能够提升网站用户体验的优秀 jQuery 插件,展示前沿的 HTML5 ...

  10. Web 前端开发精华文章推荐(HTML5、CSS3、jQuery)【系列二十三】

    <Web 前端开发精华文章推荐>2014年第2期(总第23期)和大家见面了.梦想天空博客关注 前端开发 技术,分享各类能够提升网站用户体验的优秀 jQuery 插件,展示前沿的 HTML5 ...

随机推荐

  1. vue之input输入框的几个事件

    目录 事件简介 示例 事件简介 click 点击事件,一般不会用于input输入框,会用于按钮,用于输入框就有点像focus了,当点击输入框时会触发 blur 失去焦点事件,当失去焦点时会触发. fo ...

  2. 请求被中止: 未能创建 SSL/TLS 安全通道 解决方案

    最近项目改造https,有部分请求出现"请求被中止: 未能创建 SSL/TLS 安全通道". 原因应该是,接口方变更了安全协议,而客户端并未启用该协议. 解决办法自然就是:让客户端 ...

  3. classmethod和staticmethod装饰器

    """ 两个装饰器 @classmethod 把一个对象绑定的方法,修改成为一个类方法 1.在方法中仍然可以引用类中的静态变量 2.可以不用实例化对象,就直接使用类名在外 ...

  4. Docker介绍下载安装、制作镜像及容器、做目录映射、做端口映射

    在计算机中,虚拟化(英语:Virtualization)是一种资源管理技术,是将计算机的各种实体资源,如服务器.网络.内存及存储等,予以抽象.转换后呈现出来,打破实体结构间的不可切割的障碍,使用户可以 ...

  5. 服务器数据监控监控-Zabbix

    Zabbix下载 Zabbix Sources https://www.zabbix.com/download Zabbix安装介绍 Server端 1.安装开发软件包 yum -y groupins ...

  6. 基础算法(排序 & 查找)

    快速排序.归并排序.整数二分查找.浮点数二分查找 快速排序 主要思想是分治: 确定分界点 调整范围 递归处理左右两段 代码: #include <iostream> using names ...

  7. Java中方法的定义和使用

    方法的定义和使用 注意事项: 1.方法与方法之间是 平级关系 不可以嵌套定义 2.方法的位置 可以在类{}中任意位置 3.方法定义之后 之后被调用 才能被执行 4.return 关键字的作用  返回关 ...

  8. python-SSTI模板注入

    一.python_SSTI模板注入介绍 ssti漏洞成因 ssti服务端模板注入,ssti主要为python的一些框架 jinja2 mako tornado django,PHP框架smarty t ...

  9. 真实场景sql优化持续更新(老司机必备)

    概述 下述场景,均来自实际产品线上经验,出于保密考量,所有需求场景都是仿造的,模拟遇到过的真实场景. 场景一: 统计数据(Order by 不具备唯一性导致的分页数据混乱) 需求 在实际业务场景中,我 ...

  10. qiankun vue子应用升级webpack5问题记录

    升级的方式是使用最新版本的 vue-cli 脚手架,重新创建一个新项目,然后复制 @vue/cli-xxx , vue 相关依赖最新版本到子应用项目 -> 核对babel, eslint相关配置 ...