Spring之实例化Bean(2)
创始人
2024-05-29 15:24:43
0

Spring是非常复杂的一个框架,想要一篇博客就说完实例化Bean的全流程,那将注定会是是很长很长的篇博客,说实话,换做我自己是没有耐心看完的。

本章,我将会以一个最简单的例子来阐述Spring实例化Bean的过程,下一章我们将会重点基于本章节的实例化过程,来阐释一些复杂的实例化过程。 比如:@Autowired @Resource如何注册的,以及循环依赖的大流程。

在开始今天的源码分析之前,我们需要强调几个概念,不懂没关系,先记住就好:

首先,getBean(beanName)方法就是实例化bean的方法,而且是核心方法。

其次, spring中会存在1级、2级、3级缓存的概念。1级缓存是存放已经实例化好的bean的,2级缓存和3级缓存都是存放正在实例化过程中的bean的相关信息的。

接着上一篇Spring之基于注解方式实例化BeanDefinition(1)_chen_yao_kerr的博客-CSDN博客,我们知道,在spring开始实例化之前,我们都是需要首先把java类封装成BeanDefinition对象的。

今天,我们看一下本章测试的最简单的Demo

package com.xiangxue.jack.bean;import org.springframework.stereotype.Repository;import javax.annotation.PostConstruct;@Repository
public class Dao {private String name;private String id;public String getName() {return name;}public void setName(String name) {this.name = name;}public String getId() {return id;}public void setId(String id) {this.id = id;}@PostConstruct  //相当于init-methodvoid init () {id = "001";name = "yy";}@Overridepublic String toString() {return "name :" + name + " id :" + id;}
}

今天,我就从这个最简单的类实例化开始将起。

 1. 首先进入AbstractApplicationContext的refresh方法中,我们直接跳转到实例化bean的方法处

 进入这个方法 :

 继续进入:

此时,我们见到了熟悉的面孔了。beanDefitionNames这个集合是放所有的beanDenation的name值的,然后我们根据name获取到BeanDefinition对象。 此时,我们知道,只有是单例、非抽象、非懒加载的BeanDefinition才有资格继续进行实例化操作

2. 此时,代码继续往下走,我们发现它调用了getBean方法,而本章节一开始我就说过,getBean方法就是实例化Bean的。因此,此时应该是进入了实例化bean的主流程了,逐层debug进去:

3. getBean(beanName)方法内部调用了doGetBean方法进行实例化的,继续跟进去,我们发现它是从缓存中拿的。而第一次实例化之前,肯定是拿不到任何bean的对象的。

此处,我们有必要点进去看一下getSingleton方法,看一下3级缓存。其实,就是3个集合而已。此处稍微记一下就好,本章节不会就3级缓存展开解释。

4. 此时,我们接着 doGetBean方法继续主干流程讲解。中间会有父子类合并,@DependOn的判断等等,全部跳过。我们直接来到实例化bean的核心代码处

 此时,我们需要关注2个方法,分别是getSingleton方法和createBean方法。这是一个lambda表达式, 流程就是先调用getSingleton方法,然后回调createBean方法

getSingleton:首先从1级缓存中拿,拿不到就把当前的beanDefinition的名字放入集合  singletonsCurrentlyInCreation中,表示当前beanDefinition正在实话bean;

 然后调用singletonObject = singletonFactory.getObject(); 进入到createBean方法

 

createBean : 在createBea方法中,我们会调用到doCreateBean方法, 然后在doCreateBean内部会调用到createBeanInstance方法

 

此时,我们进入到createBeanInstance方法中:其实,在这个方法中,我们知道实例化的2种方法,一个是有参构造函数,另一个是无参构造函数。而本章节的demo,我们是最简单的实例化步骤,因此是无参构造函数

 

 到此时,我们知道最终返回值是一个经过包装的BeanWrapper对象。但是,我们继续debug进去看实例化的具体步骤:

继续跟进,我们知道就是反射调用生成对象了

反射实例化完成以后,我们将对象包装成BeanWrapper对象进行返回。

5. 此时,我们还是回到了doCreateBean方法体中,继续往下走。

我们会遇到方法applyMergedBeanDefinitionPostProcessors,此处我们稍微介绍一下。这个方法其实就是搜集注解的作用。比如:@Autowired @Resource @PostConstruct等等。

这些收集注解的工作,是我们在实例化BeanDefinition的时候,通过调用registerComponents方法注进去的各种PostProcessor实现的。上一章的补充说明部分。可以直接在Spring之基于注解方式实例化BeanDefinition(1)_chen_yao_kerr的博客-CSDN博客

中搜索 “最后补充一下”。

继续回到applyMergedBeanDefinitionPostProcessors方法:

 此处,我仅仅列出了一个CommonAnnotationBeanPostProcessor作为举例。

6. 言归正传,我们还是继续doCreateBean方法, 往下走:接下来我们会遇到3个比较重要的方法,逐个列举出来解释:

addSingletonFactory : 添加缓存

populateBean: ioc di,依赖注入的核心方法。 本章是最简单的实例化过程,不涉及。下章节会重点分析。

initializeBean:实例化+ioc依赖注入完以后的调用,简单概括就是对象实例化完成以后,里面的变量,无论是通过注解注入的,还是调用什么方法初始化的,此时都没有值。本章中,我们使用到了@PostConstruct注解,那么我们此处简单的分析一下这个方法。下章节再重点分析。

进入这个方法,我们直接来到核心代码处: 

 点进去,我们发现,它又是调用我们在实例化BeanDefinition是注册的那些PostProcessor。我们使用到了@PostConstruct注解,那么我们还是以CommonAnnotationBeanPostProcessor为例进行分析:

 进入CommonAnnotationBeanPostProcessor的postProcessBeforeInitialization方法:

 这里,我们先来解释一下MetaData类。每个java都会生成一个BeanDefinition和一个MetaData对象,而MetaData里面存储了类的基本信息,比如:这个类的父类是谁,使用了哪些注解,有哪些方法,类和方法的作用域等各种各样的信息。MetaData会被大量的使用,以后我们会经常看到。

继续跟进这个方法:

此时,在我们的Dao.java文件的init方法上打上断点,代码会直接进入我们的方法进行初始化操作。因为它使用了@PostConstruct注解:

   @PostConstruct  //相当于init-methodvoid init () {id = "001";name = "yy";}

返回到 applyBeanPostProcessorsBeforeInitialization方法中,我们可以确认dao对象已经被初始化完成:

继续返回上一层方法 initializeBean 中。此时,下面还有2个比较重要的方法,本例暂时没有用到,暂时忽略。

继续返回上一层方法中,此时由来到了doCreateBean方法中了。此时,它会注册bean销毁时的类DisposableBeanAdapter,最后返回bean对象。

继续返回上一层方法中,此时来到了createBean方法中。

等等,前面好像说过createBean方法,从第4步开始介绍createBean,一直到现在才最终回到了createBean方法中,而且这个方法内部又调用了N多实例化的核心方法,值得反复研究。此时,createBean方法来到了结尾处了:

返回createBean方法的上一层:

 我们之前说过,在调用getSingleton方法的时候会回调进入createBean方法,那此时createBean方法结束了,接下来又会进入到getSingleton方法中了

继续debug方法 getSingleton 往下走:此时会完成2件事

第4步一开始的时候,我们说从一级缓存中拿不到beanName的对象,会把beanName放入一个集合中,表示这个bean正在实例化。而此时,我们实例化和初始化都完成了,需要把当前的beanName从表示正在创建的集合中删除掉。

第4步一开始的时候就说从缓存中拿beanName的对象,那时候拿不到。现在已经完成实例化了。需要将当前实例化的bean放入1级缓存,并从2、3级缓存中删除。

 

 哇塞,说了这么多,终于完成了第4步全部流程的梳理。就这么一小段代码,反反复复的调用,干了如此多的事情,真是不容易啊

	if (mbd.isSingleton()) {sharedInstance = getSingleton(beanName, () -> {try {return createBean(beanName, mbd, args);}catch (BeansException ex) {// Explicitly remove instance from singleton cache: It might have been put there// eagerly by the creation process, to allow for circular reference resolution.// Also remove any beans that received a temporary reference to the bean.destroySingleton(beanName);throw ex;}});//改方法是FactoryBean接口的调用入口bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);}

7. 好了,我们又回到了主流程方法 doGetBean 中了。接下来就是一路返回。至此,实例化Bean对象也已经全部完成了。

测试:

下面开始我们的测试

package com.xiangxue.jack;import com.xiangxue.jack.bean.Dao;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.test.context.ContextConfiguration;//@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:spring.xml"})
public class MyTest {@Autowiredprivate ApplicationContext applicationContext;@Testpublic void myBean3() {applicationContext = new ClassPathXmlApplicationContext("spring.xml");Dao dd = (Dao) applicationContext.getBean("dao");System.out.println(dd.toString());}
}

直接debug 进入getBean方法中,只要我们从1级缓存中直接拿到对象就说明整个流程梳理都是对的。

 确实是拿到了实例化并且初始化后的bean对象了,而且控制台也正确的打印了出来。测试成功

相关内容

热门资讯

122.(leaflet篇)l... 听老人家说:多看美女会长寿 地图之家总目录(订阅之前建议先查看该博客) 文章末尾处提供保证可运行...
育碧GDC2018程序化大世界... 1.传统手动绘制森林的问题 采用手动绘制的方法的话,每次迭代地形都要手动再绘制森林。这...
育碧GDC2018程序化大世界... 1.传统手动绘制森林的问题 采用手动绘制的方法的话,每次迭代地形都要手动再绘制森林。这...
Vue使用pdf-lib为文件... 之前也写过两篇预览pdf的,但是没有加水印,这是链接:Vu...
PyQt5数据库开发1 4.1... 文章目录 前言 步骤/方法 1 使用windows身份登录 2 启用混合登录模式 3 允许远程连接服...
Android studio ... 解决 Android studio 出现“The emulator process for AVD ...
Linux基础命令大全(上) ♥️作者:小刘在C站 ♥️个人主页:小刘主页 ♥️每天分享云计算网络运维...
再谈解决“因为文件包含病毒或潜... 前面出了一篇博文专门来解决“因为文件包含病毒或潜在的垃圾软件”的问题,其中第二种方法有...
南京邮电大学通达学院2023c... 题目展示 一.问题描述 实验题目1 定义一个学生类,其中包括如下内容: (1)私有数据成员 ①年龄 ...
PageObject 六大原则 PageObject六大原则: 1.封装服务的方法 2.不要暴露页面的细节 3.通过r...
【Linux网络编程】01:S... Socket多进程 OVERVIEWSocket多进程1.Server2.Client3.bug&...
数据结构刷题(二十五):122... 1.122. 买卖股票的最佳时机 II思路:贪心。把利润分解为每天为单位的维度,然后收...
浏览器事件循环 事件循环 浏览器的进程模型 何为进程? 程序运行需要有它自己专属的内存空间࿰...
8个免费图片/照片压缩工具帮您... 继续查看一些最好的图像压缩工具,以提升用户体验和存储空间以及网站使用支持。 无数图像压...
计算机二级Python备考(2... 目录  一、选择题 1.在Python语言中: 2.知识点 二、基本操作题 1. j...
端电压 相电压 线电压 记得刚接触矢量控制的时候,拿到板子,就赶紧去测各种波形,结...
如何使用Python检测和识别... 车牌检测与识别技术用途广泛,可以用于道路系统、无票停车场、车辆门禁等。这项技术结合了计...
带环链表详解 目录 一、什么是环形链表 二、判断是否为环形链表 2.1 具体题目 2.2 具体思路 2.3 思路的...
【C语言进阶:刨根究底字符串函... 本节重点内容: 深入理解strcpy函数的使用学会strcpy函数的模拟实现⚡strc...
Django web开发(一)... 文章目录前端开发1.快速开发网站2.标签2.1 编码2.2 title2.3 标题2.4 div和s...