Spring IOC容器初始化过程 源码分析

2023-11-01

本文主要记录Spring容器创建 源码分析过程。

首先贴上一张时序图,好久没画,忘的差不多了,画的不好,可以凑合看一下。
这里写图片描述

接下来,贴上一份测试代码,这里使用AnnotationConfigApplicationContext来初始化Spring容器

	@Test
	public void test1() {
		AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
		System.out.println("spring容器初始化结束");
		Person person = (Person) ctx.getBean("person");
		System.out.println(person.toString());
	}
@Configuration
public class AppConfig {

	@Bean(value="person")
	public Person getPerson() {
		Person person = new Person();
		person.setName("zhangsan");
		return person;
	}
}
public class Person {

	private String name;

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	@Override
	public String toString() {
		return "Person [name=" + name + "]";
	}
}

代码看完了,直接进入正题,开始Debug。

Debug从AnnotationConfigApplicationContext的构造器方法开始,找到该构造方法。
在这里插入图片描述
往下继续走,进入到AbstractApplicationContext的refresh()方法,Spring容器的初始化过程就在该方法中完成的。
本文会进入每个方法,看看方法里面的代码,但可能不会很细。
在这里插入图片描述
在这里插入图片描述

1、接下来,继续Debug,进入第一个方法prepareRefresh(),该方法主要是进行刷新前的预处理操作。

在这里插入图片描述

2、进入第二个方法ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(),该方法主要是获取beanFactory。

在这里插入图片描述
进入refreshBeanFactory(),会跳到GenericApplicationContext的refreshBeanFactory()方法。这里讲一下
GenericApplicationContext有个构造方法,会new一个新的DefaultListableBeanFactory。
在这里插入图片描述
而进入refreshBeanFactory()先当于new一个DefaultListableBeanFactory,并设置一个序列化值。
在这里插入图片描述

返回接在再看getBeanFactory()方法,跳到GenericApplicationContext的getBeanFactory(),返回上一步创建的DefaultListableBeanFactory
在这里插入图片描述
最后将该beanFactory(DefaultListableBeanFactory)返回。

3、接着进入第三个方法prepareBeanFactory(beanFactory),该方法主要是beanFactory的预准备工作,也就是对beanFactory进行一些初始化后的设置;
1)、设置BeanFactory的类加载器、支持表达式解析器…
2)、添加部分BeanPostProcessor(ApplicationContextAwareProcessor)
3)、设置忽略的自动装配的接口EnvironmentAware、EmbeddedValueResolverAware等;
4)、注册可以解析的自动装配,我们能直接在任何组件中自动注入:
BeanFactory、ResourceLoader、ApplicationEventPublisher、ApplicationContext
5)、添加BeanPostProcessor【ApplicationListenerDetector】
6)、添加编译时的AspectJ;
7)、往BeanFactory中注册组件;environment、 systemProperties、systemEnvironment。

4、接着往下走,进入第4个方法postProcessBeanFactory(beanFactory),发现该方法是交给子类重写的,子类通过重写这个方法来在BeanFactory创建并预准备完成以后做进一步的设置

protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {}

--------------------------------以上是BeanFactory的创建及预准备工作-----------------------------

5、继续进入第5个方法invokeBeanFactoryPostProcessors(beanFactory),该方法主要是调用beanFactory的后置处理器,在BeanFactory标准初始化之后执行的。这里我们主要跟踪一下AppConfig.class中person bean的生成。看下图,继续进入跟踪

在这里插入图片描述
在processConfigBeanDefinitions(BeanDefinitionRegistry registry)中找到下图代码,该代码就是加载person bean定义的。往后就不继续了。
在这里插入图片描述
执行完这句代码,可以看一下beanFactory中的beanDefinitionMap属性,已经多了一个person bean 定义
在这里插入图片描述
考虑到篇幅,以下几个步骤就不展示代码,只说一下方法里面的操作。
6、registerBeanPostProcessors(beanFactory)。该方法主要往beanFactory注册Bean的后置处理器。
不同接口类型的BeanPostProcessor,在Bean创建前后的执行时机是不一样的。有以下几个步骤:
一、获取所有的 BeanPostProcessor;后置处理器都默认可以通过PriorityOrdered、Ordered接口来执行优先级
二、先注册PriorityOrdered优先级接口的BeanPostProcessor;
把每一个BeanPostProcessor;添加到BeanFactory中
beanFactory.addBeanPostProcessor(postProcessor);
三、再注册Ordered接口的
四、 最后注册没有实现任何优先级接口的
五、最终注册MergedBeanDefinitionPostProcessor;
六、注册一个ApplicationListenerDetector;来在Bean创建完成后检查是否是ApplicationListener,如果是
applicationContext.addApplicationListener((ApplicationListener<?>) bean);
7、initMessageSource()。该方法主要初始化MessageSource组件(国际化功能;消息绑定,消息解析)。有以下几个步骤:
一、 获取BeanFactory
二、看容器中是否有id为messageSource的,类型是MessageSource的组件
如果有赋值给messageSource,如果没有自己创建一个DelegatingMessageSource;
MessageSource:取出国际化配置文件中的某个key的值;能按照区域信息获取;
三、 把创建好的MessageSource注册在容器中,以后获取国际化配置文件的值的时候,可以自动注入MessageSource;
beanFactory.registerSingleton(MESSAGE_SOURCE_BEAN_NAME, this.messageSource);
MessageSource.getMessage(String code, Object[] args, String defaultMessage, Locale locale);
8、initApplicationEventMulticaster();该方法主要初始化事件派发器;有以下几个步骤:
一、获取BeanFactory
二、 从BeanFactory中获取applicationEventMulticaster的ApplicationEventMulticaster;
三、如果上一步没有配置;创建一个SimpleApplicationEventMulticaster
四、 将创建的ApplicationEventMulticaster添加到BeanFactory中,以后其他组件直接自动注入
9、onRefresh();该方法主要在容器刷新的时候可以自定义逻辑,留给子类实现。
10、registerListeners();该方法主要在容器中将所有项目里面的ApplicationListener注册进来。有以下几个步骤:
一、从容器中拿到所有的ApplicationListener
二、 将每个监听器添加到事件派发器中;
getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
三、派发之前步骤产生的事件;
11 、接下来重点看一下这个方法finishBeanFactoryInitialization(beanFactory);该方法作用是实例化所有剩下的懒加载单实例。
在这里插入图片描述
重点看一下下面这段代码

	@Override
	public void preInstantiateSingletons() throws BeansException {
		if (this.logger.isDebugEnabled()) {
			this.logger.debug("Pre-instantiating singletons in " + this);
		}

		// Iterate over a copy to allow for init methods which in turn register new bean definitions.
		// While this may not be part of the regular factory bootstrap, it does otherwise work fine.
		// 获取容器中的所有Bean定义名字
		List<String> beanNames = new ArrayList<String>(this.beanDefinitionNames);

		// Trigger initialization of all non-lazy singleton beans...
		for (String beanName : beanNames) {
			1.先获取bean定义
			RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
			2.判断Bean是不是抽象的,是不是单实例的,是不是懒加载
			if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
				 判断是否是FactoryBean;
				if (isFactoryBean(beanName)) {
					final FactoryBean<?> factory = (FactoryBean<?>) getBean(FACTORY_BEAN_PREFIX + beanName);
					boolean isEagerInit;
					是否是实现FactoryBean接口的Bean
					if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
						isEagerInit = AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
							@Override
							public Boolean run() {
								return ((SmartFactoryBean<?>) factory).isEagerInit();
							}
						}, getAccessControlContext());
					}
					else {
						isEagerInit = (factory instanceof SmartFactoryBean &&
								((SmartFactoryBean<?>) factory).isEagerInit());
					}
					if (isEagerInit) {
						getBean(beanName);
					}
				}
				else {
				    //我们重点看一下这个方法。不是工厂bean,通过该方法获取创建bean实例
					getBean(beanName);
				}
			}
		}
		xxxxxx

当循环的beanName 为person的时候,我们debug进这个getBean(beanName)方法。
在这里插入图片描述
继续debug,进入方法。因为person实例还没有创建,beanFactory中的singletonObjects肯定找不到。一些判断这里就不展示,直接跳过了。找到下面代码
在这里插入图片描述
继续debug,进入createBean(beanName, mbd, args);
在这里插入图片描述
找到下面代码,进入doCreateBean(beanName, mbdToUse, args);
debug了半天,终于要创建bean实例了。这里Spring容器使用BeanWrappe。BeanWrapper是对Bean的包装,大部分情况下是在spring ioc内部进行使用,用来设置获取被包装的bean对象,获取被包装bean的属性描述器等。
在这里插入图片描述
Debug进入createBeanInstance(beanName, mbd, args);
在这里插入图片描述
进入debug,进入。通过工厂方法来实例化
在这里插入图片描述
继续进入,找到下面代码。beanFactory通过实例化策略来实例化bean
在这里插入图片描述
继续debug,进入。找到图中的代码,箭头标记的方法,就是最后生成bean的方法,通过工作方法调用生成实例bean。
在这里插入图片描述

执行完上图箭头代码factoryMethod.invoke(factoryBean, args),bean实例已经生成了,控制台会打印下面这句话。
在这里插入图片描述
一路返回,返回到
AbstractAutowireCapableBeanFactory的 doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args)方法。想方便点的话,断点直接打到箭头标记的那行,就不需要一路return回去了。这时候可以看到实例bean的信息。
在这里插入图片描述
往下继续走,找到下图的代码,箭头中的方法是对实例bean进行赋值。里面有赋值之前的一些后置处理器的处理和利用setter方法等为属性进行赋值操作。这里简单介绍下。
在这里插入图片描述
往下看到initializeBean(beanName, exposedObject, mbd)方法。在该方法中进行bean的初始化。里面也是一些后置处理器的操作。
然后,继续返回出去,来到AbstractBeanFactory的doGetBean方法。
在这里插入图片描述
这个方法上面见过了,之前只对图中的createBean(beanName, mbd, args)进行分析。现在我们看一下getSingleton(…)这个方法。这个方法也简单,就是从beanFactory的singletonObjects(Map)属性中获取该beanName的值,如果存在,则返回;否则,将该beanName和对应的bean实例添加到singletonObjects属性中。
在这里插入图片描述
在这里插入图片描述

12、最后看一下refresh() 方法里面的最后一个方法finishRefresh()。该方法主要完成BeanFactory的初始化创建工作。这样IOC容器就创建完成了。

在这里插入图片描述

经过上面n多步骤,spring ioc容器初始化过程就结束了


Person person = (Person) ctx.getBean("person");

接下来这个获取person bean就简单了。它的原理就是从beanFactory的singletonObjects(map)中,通过key,获取value了。源码上面也看过了。


学习源码的过程是枯燥的,由于是全英文,可能看着也比较累,但是学习完,感觉收获还是颇多了。由于这个初始化过程步骤比较多,涉及到的东西也比较多,这里看一下,那里看一下,多个类之间切来切去,可能看着也会比较乱一点。但是多看几遍,相信大家也会对这个过程比较了解了。

个人总结:从上面几个步骤可以看出,IOC容器初始化的过程,大部分都是往ioc容器的诸多Map中添加值,方便后续读取使用。读取的时候只需要从Map中根据key取值就可以了。

到此,本篇文章也就结束了。该篇文章主要是记录本人学习spring ioc容器源码的一个过程。中间可能存在一些问题,或有一些不够严谨完善的地方,希望大家体谅体谅。有问题的话,欢迎大家留意,交流交流。


本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Spring IOC容器初始化过程 源码分析 的相关文章

随机推荐

  • kvm在虚拟服务器,江博士带你轻松搞定KVM虚拟化

    阅读 3 609 在虚拟化中 必然存在宿主机与虚拟机通信的需要 一般来说 可以通过网桥的方式与虚拟机建立通信通道 通过网络传输数据 适用性比较强 但是 这种方式依赖于网络通畅 当虚拟机发生某些故障 网络已经发生问题 宿主机就需要另外一种方式
  • Java 多线程 --- 线程同步 显式锁ReentrantLock

    Java 多线程 线程同步 显式锁ReentrantLock 和 AQS机制 显式锁 Lock接口 显性锁的实现原理 AQS机制 显式锁 Lock接口 显式锁是自 JDK 1 5 开始引人的排他锁 作为一种线程同步机制 其作用与内部锁相 同
  • react与vue的区别

    React和Vue js是两个流行的JavaScript库 框架 用于构建用户界面 以下是React和Vue之间的一些主要区别 学习曲线 Vue js对于新手来说比React更容易学习和上手 构建方式 React强调组件的可重用性 而Vue
  • libevent HTTPserver/ libcurl HTTPClient构建

    22 1 前言 最近由于项目需要实现http客户端与http服务器 要求是httpServer httpClient模块独立 不能与用户代码耦合一起 因此为了使得httpserver及httpclient的通用性 在软件框架搭建中httpS
  • 利用SQLmap进行一次SQL注入

    题目 ctf show 题目博客很久之前已经做过一次了 很简单 这次只是用它来记录一下sqlmap如何使用 首先进入题目 是一个登录界面 猜测是sql注入 打开SQLmap 查询网站是否存在注入 burp抓包 将http头部文件复制到sql
  • python学习笔记十——模块与函数

    第五章 模块与函数 5 1 python程序的结构 函数 类 gt 模块 模块 模块 gt 包 函数 类 模块 包 Python python的程序由包 package 模块 module 和函数组成 模块是处理某一类问题的集合 模块由函数
  • Excelize 是 Go 语言编写的用于操作 Office Excel 文档基础库

    介绍 Excelize 是 Go 语言编写的用于操作 Office Excel 文档基础库 基于 ECMA 376 ISO IEC 29500 国际标准 可以使用它来读取 写入由 Microsoft Excel 2007 及以上版本创建的电
  • driverclasss oracle,Kettle 错误:Error connecting to database: (using class oracle.jdbc.driver.OracleDr...

    一 问题描述 使用kettle8 0连接oracle12c 发现将oracle安装目录下与ojdbc相关的jar包都拷贝到kettle的相关目录Program Files kettle data integration lib下 配置连接
  • 清华源镜像pip

    清华源镜像使用帮助 彻底解决timeout的问题 临时使用 pip install i https pypi tuna tsinghua edu cn simple some package opencv python 注意 末尾openc
  • Windows上提示 api-ms-win-core-path-l1-1-0.dll 丢失怎么办?

    Windows上提示 api ms win core path l1 1 0 dll 丢失怎么办 最近有用户在开启电脑的photoshop软件使用的时候 出现另外无法启动软件的情况 因为系统中缺失了对应的dll文件 那么这个情况怎么去进行问
  • 电源学习总结(三)——线性稳压的参数

    前文提到了一些线性稳压的主要特点 本文作者将结合NXP智能车大赛实际案例 说一下电源设计容易踩的坑和线性稳压选型过程 文章目录 主要参数 输出电压 输入电压 热阻 基准电压 压降 最大电流 输入电容 纹波抑制 选型实例 主要参数 在为实际应
  • 二十八.刷题.18

    输入三个字符 可以重复 后 按各字符的ASCII码从小到大的顺序输出这三个字符 include
  • 解密 QQ 号-队列-c语言

    问题描述 分析 每次从最前面拿两个 第 1 个扔掉 第 2 个 放到尾部 需要一个数组来存储这一串数即 int q 101 并初始化这个数组即 int q 101 0 6 3 1 7 5 8 9 2 4 head 用来记录队列的队首 即第一
  • 动态规划-货币问题

    动态规划 货币问题 题目一 arr是货币数组 其中的值都是正数 再给定一个正数aim 每个值都认为是一张货币 即便是值相同的货币也认为每一张都是不同的 返回组成aim的方法数 例如 arr 1 1 1 aim 2 第0个和第1个能组成2 第
  • 机器学习 之线性回归(包含推导过程)

    参考B站视频新手狂喜 目前B站最全最清晰的 机器学习算法 教程 从零开始详细解读 原理 代码实现 通通都在这里了 收藏慢慢学 决策树 随机森林 聚类分析 人工智能 哔哩哔哩 bilibili 线性回归 eg 银行贷款 数据 工资和年龄 特征
  • C Primer Plus(第六版)

    1 开发一个包含你需要的预处理器定义的头文件 写一个 h的头文件 包含你此次练习的题目就可以了 注意防止头文件被重复引用导致的错误 需要用 ifndef或者 pragma once 下面题目需要的声明和结构都在对应题目中 方法1 使用 if
  • mysql使用查询结果作为临时表

    一 select查询作为临时表 select cou name from select count AS cou enabled AS name from user group by enabled as a where cou gt 0
  • 基于微信小程序的新冠疫苗预约系统

    末尾获取源码 开发语言 Java Java开发工具 JDK1 8 后端框架 SSM 前端 微信小程序 Vue 数据库 MySQL5 7和Navicat管理工具结合 服务器 Tomcat8 5 开发软件 IDEA Eclipse 是否Mave
  • ArrayList集合及常用方法的使用

    ArrayLise
  • Spring IOC容器初始化过程 源码分析

    本文主要记录Spring容器创建 源码分析过程 首先贴上一张时序图 好久没画 忘的差不多了 画的不好 可以凑合看一下 接下来 贴上一份测试代码 这里使用AnnotationConfigApplicationContext来初始化Spring