《系列二》-- 6、从零开始的 bean 创建
阅读之前要注意的东西:本文就是主打流水账式的源码阅读,主导的是一个参考,主要内容需要看官自己去源码中验证。全系列文章基于 spring 源码 5.x 版本。
写在开始前的话:
阅读spring 源码实在是一件庞大的工作,不说全部内容,单就最基本核心部分包含的东西就需要很长时间去消化了:
- beans
- core
- context
实际上我在博客里贴出来的还只是一部分内容,更多的内容,我放在了个人,fork自 spring 官方源码仓了; 而且对源码的学习,必须是要跟着实际代码层层递进的,不然只是干巴巴的文字味同嚼蜡。
这个仓设置的公共仓,可以直接拉取。
createBean() 的面纱
我们在 AbstractBeanFactory 里找到的 createBean() 只是个 抽象方法,如果使用 IDEA 的朋友,可以:
- Ctrl + Alt + 鼠标左键
最终在它的子类 AbstractAutowireCapableBeanFactory 中找到的 createBean() 方法的实现
- String beanName: bean名称
- RootBeanDefinition mbd: bean配置元数据
- @Nullable Object[] args: 不认识的一律当作空的处理,它可能只在某些特殊场景下被使用
下边是官方源码 + 个人添加的注释:
/**
* Central method of this class: creates a bean instance,
* populates the bean instance, applies post-processors, etc.
* @see #doCreateBean
*/
@Override
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
throws BeanCreationException {
if (logger.isDebugEnabled()) {
logger.debug("Creating instance of bean '" + beanName + "'");
}
// 解析过程实际用到的 bean元数据
RootBeanDefinition mbdToUse = mbd;
// Make sure bean class is actually resolved at this point, and
// clone the bean definition in case of a dynamically resolved Class
// which cannot be stored in the shared merged bean definition.
// 笼统的说:根据bean元数据(BeanDefinition) 获取 bean 的class
// 1 若元数据中包含了 类class对象,直接返回。
// 2 否则获取线程的 ClassLoader 根据 bean的 ClassName 得到“类的Class对象”,这个解析过程同样涉及到了缓存,
// 这个 “类的Class”对象,会被当作缓存保存到 bean的元数据(BeanDefinition 中),
// 也就是第一不提到的情形,实际上可能就是 “多例”bean 加载是在重复利用 bean元数据。
Class<?> resolvedClass = resolveBeanClass(mbd, beanName);
if (resolvedClass != null && !mbd.hasBeanClass() && mbd.getBeanClassName() != null) {
// 套娃验证 + 类缓存机制; spring 代码的健壮性可见一斑
mbdToUse = new RootBeanDefinition(mbd);
mbdToUse.setBeanClass(resolvedClass);
}
// Prepare method overrides.
try {
// replace-method / lookup-method 的有效性校验 <若存在>
mbdToUse.prepareMethodOverrides();
}
catch (BeanDefinitionValidationException ex) {
throw new BeanDefinitionStoreException(mbdToUse.getResourceDescription(),
beanName, "Validation of method overrides failed", ex);
}
// 实例化 | 初始化
try {
// Give BeanPostProcessors a chance to return a proxy instead of the target bean instance.
// 调用 "实例化前 - 后置处理器", 解析指定 bean 是否存在初始化前的短路操作
// 因为我们并没有往 XmlBeanFactory 配置,代表任何行为的 "后置处理器" 所以这一步可以跳过
Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
if (bean != null) {
// 短路处理,如果:实例化-前置处理, 返回结果不为空,直接将其作为处理结果返回,不再执行: doCreateBean(....)
return bean;
}
}
catch (Throwable ex) {
throw new BeanCreationException(mbdToUse.getResourceDescription(), beanName,
"BeanPostProcessor before instantiation of bean failed", ex);
}
try {
// 见到 doCreateBean() 代表进入正文了,前边讲循环依赖,讲单例bean时,提到了"三级缓存",其中优先级最低的 "第三级" 缓存就是从
// doCreateBean() 方法中注入的
Object beanInstance = doCreateBean(beanName, mbdToUse, args); // 创建 bean
if (logger.isDebugEnabled()) {
logger.debug("Finished creating instance of bean '" + beanName + "'");
}
return beanInstance;
}
catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) {
// A previously detected exception with proper bean creation context already,
// or illegal singleton state to be communicated up to DefaultSingletonBeanRegistry.
throw ex;
}
catch (Throwable ex) {
throw new BeanCreationException(
mbdToUse.getResourceDescription(), beanName, "Unexpected exception during bean creation", ex);
}
}
上边的代码有提到一个概念:实例化前 - 后置处理器,若不了解相关概念可参见下文
本节的介绍止于: doCreateBean()
接下来我们继续......
createBean() 的承包者: doCreateBean()
它除了是 createBean() 的包工头之外,它还是 "三级缓存" 机制的起点;
"三级缓存" 和 "循环依赖的消解" 也是个大活呢。
老规矩,同样是代码加 + 注释的方式展开:
/**
* Actually create the specified bean. Pre-creation processing has already happened
* at this point, e.g. checking {@code postProcessBeforeInstantiation} callbacks.
* <p>Differentiates between default bean instantiation, use of a
* factory method, and autowiring a constructor.
*
* @param beanName the name of the bean
* @param mbd the merged bean definition for the bean
* @param args explicit arguments to use for constructor or factory method invocation
* @return a new instance of the bean
* @throws BeanCreationException if the bean could not be created
* @see #instantiateBean
* @see #instantiateUsingFactoryMethod
* @see #autowireConstructor
*/
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
throws BeanCreationException {
// Instantiate the bean.
BeanWrapper instanceWrapper = null;
if (mbd.isSingleton()) {
// ConcurrentMap<String, BeanWrapper> factoryBeanInstanceCache
// 1 并发场景下起手式: 单例bean先尝试捞缓存
instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
}
if (instanceWrapper == null) {
// 2 缓存中没捞到?唔好意思,只能从头开始解析了。根据 beanName 使用对应策略创建 bean, 例如:工厂方法、构造函数自动注入
// 简单来说就是:bean元数据(BeanDefinition),合法性校验: 匹配参数、构造函数/factory-bean, 应用BeanFactory
// 的实例化策略, 实例化指定bean
// 返回的bean 是由 Cglib 动态代理生成的,可以参考包装器模式去理解它
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
// 从包装器中获取 bean 实例
Object bean = instanceWrapper.getWrappedInstance();
Class<?> beanType = instanceWrapper.getWrappedClass();
if (beanType != NullBean.class) {
// 这里对 bean元数据属性的设置,八九不离十是缓存 (我们在前边搞不好已经见过它也说不定)
mbd.resolvedTargetType = beanType;
}
// MergedBeanDefinitionPostProcessor 应用
// Allow post-processors to modify the merged bean definition.
synchronized (mbd.postProcessingLock) { // 对 BeanDefinition 对象上锁
if (!mbd.postProcessed) { // 双重锁机制,避免重复
try {
// PostProcessor ? 那么明显是个 "后置处理器",不妨从命名猜测下:
// MergedBeanDefinition ? 走到这一步我们已经获取到了bean的实例,那么八九不离十,这里是根据bean的解析结果对
// mbd(bean元数据) 进行类似缓存设置相关的操作、或者进行一些检查性质类似的操作
// 接下来我们进去一探究竟
applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
} catch (Throwable ex) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Post-processing of merged bean definition failed", ex);
}
// 双重锁释放
mbd.postProcessed = true;
}
}
// 4 依赖处理
// 这里做了一件事:利用条件进行判断,当前指定加载的bean是否允许提早曝光
// Eagerly cache singletons to be able to resolve circular references
// even when triggered by lifecycle interfaces like BeanFactoryAware.
// 浅浅的按个人的理解翻译下这句话:急切的进行单例缓存,即使在bean的较早的生命周期环节,也允许被循环依赖。
// 这里提到了生命周期,那么可能是在警示 allowCircularReferences 的使用 ,如果开启它,可能导致 bean 在不完全加载时被循环依赖
// 条件: bean是单例 && 允许循环依赖 && bean正在创建 >>> 结果: 是否允许提早曝光
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
if (logger.isDebugEnabled()) {
logger.debug("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
// 三级缓存:它不就来了么? addSingletonFactory方法操作的: singletonFactories 难道不是老熟人么?
// 如果已经忘了可以再去看看,本系列第五篇:单例bean缓存的获取
// 提早曝光: 初始化完成前,将创建包含bean实例的 ObjectFactory对象提早曝光加入工厂
// -> (beanName,new ObjectFactory())
addSingletonFactory(beanName, () -> {
// 应用 InstantiationAwareBeanPostProcessor,在此过程中涉及将AOP的advice,动态织入bean,若无AOP配置则直接返回bean
// 记得全局搜索这个关键词: (SmartInstantiationAwareBeanPostProcessor) 不止提前暴露bean的时候执行这些 "后置处理器"
// 我们定会重逢的
// 此处,当三级缓存被获取时,ObjectFactory().getObject() 执行的内容,最终将由如下的方法承包
return getEarlyBeanReference(beanName, mbd, bean);
});
}
// Initialize the bean instance. 【初始化:实例化得到 bean 对象,然后通过初始化操作,进行bean属性的填充。】
Object exposedObject = bean;
try {
// 虽然:bean = instanceWrapper.getWrappedInstance(); 但是需要注意的是,如果bean 被提前暴露了,这里的 bean 可能是被:
// 后置处理器: SmartInstantiationAwareBeanPostProcessor 加工之后的结果
// 这取决于 SmartInstantiationAwareBeanPostProcessor 返回的结果是否还是原始的bean对象。 (记录为保留问题)
// 5 属性填充 bean (初始化) 将依赖属性注入,如果引用别的 bean 则递归的去进行 bean 的 初始化
// 方法名直译就是:填充bean,这里的操作基本上,就是从 bean 的元数据 mdb上读取相关属性,并设置到新的 bean 上
populateBean(beanName, mbd, instanceWrapper);
// 调用 bean.xml中定义的初始化方法,例如: method-init 等等
exposedObject = initializeBean(beanName, exposedObject, mbd);
} catch (Throwable ex) {
if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
throw (BeanCreationException) ex;
} else {
throw new BeanCreationException(
mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
}
}
// 6 循环依赖 检查
// 检查已经加载的bean 是否存在 非 setter 循环依赖
// 单例循环依赖检查 可通过配置消解
if (earlySingletonExposure) { // 如果当前bean 被提前暴露
// 应用三级缓存,读取被提前暴露的 bean
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
// 缓存读取结果不为空,那么说明存在循环依赖
if (exposedObject == bean) {
// 这里的判断结果表明: 处理循环依赖时提前暴露的bean 跟 被一系列后置处理器加工过后的bean 的 "对象堆内存地址一致"
// 说明 "后置处理器" 的加工,没有导致原始 bean 对象,没有被替换
exposedObject = earlySingletonReference;
}
// allowRawInjectionDespiteWrapping : 在循环引用的情况下,是否需要注入原始bean实例,即使注入的bean最终被包装。
else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
// 对象地址不等,说明加工过程中,原始的 bean 对象,已经被偷梁换柱了
// 获取当前指定bean 依赖的别的bean名称
String[] dependentBeans = getDependentBeans(beanName);
Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
for (String dependentBean : dependentBeans) {
// 依赖检测
// dependentBeans 包含的时当前bean 所依赖其它bean
// 1. removeSingletonIfCreatedForTypeCheckOnly = true 表示当前 bean,循环依赖了别的尚处于创建的中的 bean
// <等同提前暴露>
// 2. removeSingletonIfCreatedForTypeCheckOnly = false, 表示依赖结构中无环,当前指定bean依赖的其它bean
// 都已经被完全成功的加载
if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
actualDependentBeans.add(dependentBean);
}
}
// actualDependentBeans 为空说明依赖不成环,或者依赖的bean 全部都已经被加载成功
// actualDependentBeans 不为空,说明有依赖的bean 未完全加载,那么必定存在循环依赖
if (!actualDependentBeans.isEmpty()) {
// 当前指定bean 存在循环依赖,依赖的其它bean未加载完全
// allowRawInjectionDespiteWrapping 为 false 时:
// 若原始bean对象被后置处理器替换 && 原始bean 已经被当作循环依赖注入了别的bean中
// 那么抛出异常,bean 加载失败
throw new BeanCurrentlyInCreationException(beanName,
"Bean with name '" + beanName + "' has been injected into other beans [" +
StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
"] in its raw version as part of a circular reference, but has eventually been " +
"wrapped. This means that said other beans do not use the final version of the " +
"bean. This is often the result of over-eager type matching - consider using " +
"'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.");
}
}
}
}
// Register bean as disposable.
try {
// 如果配置了 destroy-method 这里需要注册,以保证对象实例销毁时调用
registerDisposableBeanIfNecessary(beanName, bean, mbd);
} catch (BeanDefinitionValidationException ex) {
throw new BeanCreationException(
mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
}
return exposedObject;
}
别看只有这一个方法,但是里面包含的东西太多太多了:
- bean 实例化,默认实例化策略: Cglib <动态代理>
- 根据 factory-method 或者 factory-bean实例化; 若如相关配置,则配置的应用构造函数,实例化bean;
若没有配置,构造函数,则应用无参构造函数实例化bean。
虽然这里只有轻飘飘的一句话: 实例化,但是实际操作时却包罗万象; 比如:
a. 应用缓存策略,先确定该 bean 是否被解析过;如果则进入如下的流程;
b. 根据参数个数从 元数据(BeanDefinition) 中匹配: factory-method / 构造函数
c. 参数 [ 类型转换 / 值解析 ]
bean 元数据解析后,如若是第一次被解析,将解析的中间结果,以缓存的形式,在 (BeanDefinition) 中保存
单例bean: 判断是否允许循环依赖, 如若允许提前暴露,将其置入三级缓存
属性填充、应用: "实例化后置处理器"
populateBean
- autowireByName
- autowireByType
详细内容见:
- 初始化方法应用: bean.xml 配置的 init-method
initializeBean
- invokeAwareMethods
- applyBeanPostProcessorBeforeInitialization
- invokeInitMethod
- applyBeanPostProcessorAfterInitialization
如果 bean 已经被提前暴露,那么判断:三级缓存中的bean对象 和 被后置处理器加工之后的bean对象,
堆内存地址是否一致:- 如若不一致,还需要应用BeanFactory的配置:allowRawInjectionDespiteWrapping,该配置决定:
- "实例化后置处理器" 替换了原始bean时,是否允许循环依赖
- 如若不一致,还需要应用BeanFactory的配置:allowRawInjectionDespiteWrapping,该配置决定:
如果元数据 bean.xml 中配置了,destroy-method,那么需要同样需要注册该方法到 bean中
以确保bean的生命周期结束后,能应用正确的销毁动作
总结
本文到此为止,几乎已经讲完了,如何从零开始创建一个 bean;由于篇幅问题,更多细节并未在上述文中完全得到体现。
如下是本人一边阅读源码,一边做笔记的个人 spring 代码仓;如若想要了解更多细节,不妨拉取下方源码,一探究竟。
- 声明:代码中的注释仅仅代表个人观点,该代码仓也仅用作个人学习所用,如有谬误感谢指正。
《系列二》-- 6、从零开始的 bean 创建的更多相关文章
- Spring框架系列(二)--装配和注入Bean
企业日常开发中,几乎都是Spring系的框架,无论是SSM.还是现在大火的SpringBoot+JPA/MyBatis,使用最大的目的就是简化开发 基本模块: 核心容器:Beans.Core.Cont ...
- 【微服务】之二:从零开始,轻松搞定SpringCloud微服务系列--注册中心(一)
微服务体系,有效解决项目庞大.互相依赖的问题.目前SpringCloud体系有强大的一整套针对微服务的解决方案.本文中,重点对微服务体系中的服务发现注册中心进行详细说明.本篇中的注册中心,采用Netf ...
- Spring源码系列(二)--bean组件的源码分析
简介 spring-bean 组件是 Spring IoC 的核心,我们可以使用它的 beanFactory 来获取所需的对象,对象的实例化.属性装配和初始化等都可以交给 spring 来管理. 本文 ...
- Storm系列(二):使用Csharp创建你的第一个Storm拓扑(wordcount)
WordCount在大数据领域就像学习一门语言时的hello world,得益于Storm的开源以及Storm.Net.Adapter,现在我们也可以像Java或Python一样,使用Csharp创建 ...
- [Unity3D插件]2dtoolkit系列二 动画精灵的创建以及背景图的无限滚动
经过昨天2dtoolkit系列教程一的推出,感觉对新手还有有一定的启发作用,引导学习使用unity 2dToolKit插件的使用过程,今天继续系列二——动画精灵的创建,以及背景图的无限循环滚动,在群里 ...
- VSTO之旅系列(二):创建Excel解决方案
原文:VSTO之旅系列(二):创建Excel解决方案 本专题概要 引言 创建VSTO项目 Excel对象模型 创建Excel外接程序 创建Excel文档级自定义项 小结 一.引言 也许很多朋友都没有听 ...
- windows下mongodb基础玩法系列二CURD操作(创建、更新、读取和删除)
windows下mongodb基础玩法系列 windows下mongodb基础玩法系列一介绍与安装 windows下mongodb基础玩法系列二CURD操作(创建.更新.读取和删除) windows下 ...
- Spring读书笔记——bean创建(下)
有关Spring加载bean系列,今天这是最后一篇了,主要接上篇对于从Spring容器中获取Bean的一些细节实现的补充. <Spring读书笔记--bean加载>--Spring如何加载 ...
- Android高效率编码-第三方SDK详解系列(二)——Bmob后端云开发,实现登录注册,更改资料,修改密码,邮箱验证,上传,下载,推送消息,缩略图加载等功能
Android高效率编码-第三方SDK详解系列(二)--Bmob后端云开发,实现登录注册,更改资料,修改密码,邮箱验证,上传,下载,推送消息,缩略图加载等功能 我的本意是第二篇写Mob的shareSD ...
- swoft| 源码解读系列二: 启动阶段, swoft 都干了些啥?
date: 2018-8-01 14:22:17title: swoft| 源码解读系列二: 启动阶段, swoft 都干了些啥?description: 阅读 sowft 框架源码, 了解 sowf ...
随机推荐
- [转帖]Oracle 如何列出正在运行的定时任务
https://geek-docs.com/oracle/oracle-questions/569_oracle_how_can_i_list_the_scheduled_jobs_running_i ...
- 【转帖】读懂什么是RDMA
一.什么是RDMA1.RDMA主要体现 2.如何理解RDMA和TCP技术的区别?3.使用RDMA的好处包括: 二.什么是RoCE?1. RDMA协议包含:Infiniband(IB)2. 为什 ...
- [转帖]一张图搞定redis内存优化及配置
https://www.jianshu.com/p/3195663af83e Redis内存优化及配置.png Redis优化及配置 Redis所有的数据都在内存中,而内存又是非常宝贵的资源.常用 ...
- buildkit ctr 与 k3s的简单学习
摘要 前面一部分学习了 buildkit的简单搭建 也学习会了如果build images的简单处理 但是搭建镜像只是万里长征第一步. 如何进行微服务部署,才是关键的第二步. 公司最近使用基于K3S的 ...
- echarts控制柱状图柱条的宽度
barWidth属性 series: [{ name: '销量', type: 'bar', barWidth : 30,//柱图宽度 data: [5, 20, 36, 10, 10, 20] }]
- 【踩了一个坑】为什么 golang struct 中的 slice 无法原子赋值
作者:张富春(ahfuzhang),转载时请注明作者和引用链接,谢谢! cnblogs博客 zhihu Github 公众号:一本正经的瞎扯 有这样一个结构体: type MySt struct{ F ...
- ABP-VNext 用户权限管理系统实战02---用户权限表的创建与迁移
一.表实体建立 1.菜单表 [Comment("菜单表")] [Table("t_identity_menu")] public class Menu : Au ...
- linux root用户密码输入正确还是提示access denied
问题:之前用远程工具连接一直都是好的,第二天上班找开远程工具要输root的密码了,输入用户密码后还是无效,可以确定用户密码是对的,其中有一个远程工具一直是连着的就没有问题. 排查问题: 1.相接用pa ...
- Vue基础系列文章05----babel安装与使用
1.安装babel-node包,运行: 1) npm install --save-dev @babel/core @babel/cli @babel/preset-env @babel/node 2 ...
- vim 从嫌弃到依赖(15)——寄存器
在计算机里面也有寄存器,计算机中的寄存器是看得见,摸得着的实体,寄存器中存储需要经常访问的一些数据.而vim中也有寄存器的概念,vim中的寄存器是一个虚拟的概念,更像是一块专门用来存储数据的内存缓冲区 ...