Spring注解驱动开发第41讲——Spring IOC容器创建源码解析(一)之BeanFactory的创建以及预准备工作
发布日期:2021-06-30 17:56:25 浏览次数:3 分类:技术文章

本文共 7397 字,大约阅读时间需要 24 分钟。

文章目录

写在前面

在前面,我们已经学会了怎样来使用ApplicationListener,也研究了一下其内部原理。而从这一讲开始,我们就要结合我们以前学过的所有内容,来梳理一下Spring整个容器的创建以及初始化过程。我是希望通过对Spring源码的整个分析,令大家对Spring内部的工作原理以及运行机制能有一个更深刻的理解。

接下来,我们来分析并详细记录一下Spring容器的创建以及初始化过程。

BeanFactory的创建以及预准备工作

我们先来看一下如下的一个单元测试类(例如IOCTest_Ext)。

package com.meimeixia.test;import org.junit.Test;import org.springframework.context.ApplicationEvent;import org.springframework.context.annotation.AnnotationConfigApplicationContext;import com.meimeixia.ext.ExtConfig;public class IOCTest_Ext {
@Test public void test01() {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(ExtConfig.class); // 发布一个事件 applicationContext.publishEvent(new ApplicationEvent(new String("我发布的事件")) {
}); // 关闭容器 applicationContext.close(); }}

我们知道如下这样一行代码是来new一个IOC容器的,而且还可以看到传入了一个配置类。

AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(ExtConfig.class);

我们不妨点进去AnnotationConfigApplicationContext类的有参构造方法里面去看一看,如下图所示,相信大家对该有参构造方法是再熟悉不过了。

在这里插入图片描述

由于我们现在是来分析Spring容器的创建以及初始化过程,所以我们将核心的关注点放在refresh方法上,也即刷新容器。该方法运行完以后,容器就创建完成了,包括所有的bean对象也都创建和初始化完成了。

接下来,我们在刷新容器的方法上打上一个断点,如下图所示,重点分析一下刷新容器这个方法里面到底做了些什么事。

在这里插入图片描述

我们以debug的方式运行IOCTest_Ext测试类中的test01方法,如下图所示,程序现在停到了标注断点的refresh方法处。

在这里插入图片描述

按下F5快捷键进入refresh方法里面,如下图所示,可以看到映入眼帘的是一个线程安全的锁机制,除此之外,你还能看到第一个方法,即prepareRefresh方法,顾名思义,它是来执行刷新容器前的预处理工作的。

在这里插入图片描述

那么问题来了,刷新容器前的这个预处理工作它到底都做了哪些事呢?下面我们就来详细说说。

prepareRefresh():刷新容器前的预处理工作

按下F6快捷键让程序往下运行,运行到prepareRefresh方法处时,按下F5快捷键进入该方法里面,如下图所示,可以看到会先清理一些缓存,我们的关注点不在这儿,所以略过。

在这里插入图片描述

继续按下F6快捷键让程序往下运行,运行到super.prepareRefresh()这行代码处,这儿也是来执行刷新容器前的预处理工作的。按下F5快捷键进入该方法里面,如下图所示,我们可以看到它里面都做了些什么预处理工作。

在这里插入图片描述

发现就是先记录下当前时间,然后设置下当前容器是否是关闭、是否是活跃的等状态,除此之外,还会打印当前容器的刷新日志。如果你要是不信的话,那么可以按下F6快捷键让程序往下运行,直至运行到initPropertySources方法处,你便能看到Eclipse控制台打印出了一些当前容器的刷新日志,如下图所示。

在这里插入图片描述

这时,我们看到了第一个方法,即initPropertySources方法。那么,它里面做了些啥事呢?

initPropertySources():子类自定义个性化的属性设置的方法

顾名思义,该方法是来初始化一些属性设置的。那么,该方法里面究竟做了些啥事呢?我们不妨进去一探究竟,按下F5快捷键进入该方法中,如下图所示,发现它是空的,没有做任何事情。

在这里插入图片描述

但是,我们要注意该方法是protected类型的,这意味着它是留给子类自定义个性化的属性设置的。例如,我们可以自己来写一个AnnotationConfigApplicationContext的子类,在容器刷新的时候,重写这个方法,这样,我们就可以在子类(也叫子容器)的该方法中自定义一些个性化的属性设置了。

这个方法只有在子类自定义的时候有用,只不过现在它还是空的,里面啥也没做。

getEnvironment().validateRequiredProperties():获取其环境变量,然后校验属性的合法性

继续按下F6快捷键让程序往下运行,直至运行到以下这行代码处。

在这里插入图片描述

这行代码的意思很容易知道,前面不是自定义了一些个性化的属性吗?这儿就是来校验这些属性的合法性的。

那么是怎么来进行属性校验的呢?首先是要来获取其环境变量,你可以按下F5快捷键进入getEnvironment方法中去看看,如下图所示,可以看到该方法就是用来获取其环境变量的。

在这里插入图片描述

继续按下F6快捷键让程序往下运行,让程序再次运行到getEnvironment().validateRequiredProperties()这行代码处。然后,再次按下F5快捷键进入validateRequiredProperties方法中去看看,如下图所示,可以看到就是使用属性解析器来进行属性校验的。

在这里插入图片描述

只不过,我们现在没有自定义什么属性,所以,此时并没有做任何属性校验工作。

保存容器中早期的事件

继续按下F6快捷键让程序往下运行,直至运行到以下这行代码处。

在这里插入图片描述

这儿是new了一个LinkedHashSet,它主要是来临时保存一些容器中早期的事件的。如果有事件发生,那么就存放在这个LinkedHashSet里面,这样,当事件派发器好了以后,直接用事件派发器把这些事件都派发出去。

总结一下就是一句话,即允许收集早期的容器事件,等待事件派发器可用之后,即可进行发布

至此,我们就分析完了prepareRefresh方法,以上就是该方法所做的事情。我们发现这个方法和BeanFactory并没有太大关系,因此,接下来我们还得来看下一个方法,即obtainFreshBeanFactory方法。

obtainFreshBeanFactory():获取BeanFactory对象

继续按下F6快捷键让程序往下运行,直至运行至以下这行代码处。

在这里插入图片描述

可以看到一个叫obtainFreshBeanFactory的方法,顾名思义,它是来获取BeanFactory的实例的。接下来,我们就来看看该方法里面究竟做了哪些事。

refreshBeanFactory():创建BeanFactory对象,并为其设置一个序列化id

按下F5快捷键进入该方法中,如下图所示,可以看到其获取BeanFactory实例的过程是下面这样子的。

在这里插入图片描述

发现首先调用了一个叫refreshBeanFactory的方法,该方法见名思义,应该是来刷新BeanFactory的。那么,该方法里面又做了哪些事呢?

我们可以按下F5快捷键进入该方法中去看看,如下图所示,发现程序来到了GenericApplicationContext类里面。

在这里插入图片描述

而且,我们还可以看到在以上refreshBeanFactory方法中,会先判断是不是重复刷新了。于是,我们继续按下F6快捷键让程序往下运行,发现程序并没有进入到if判断语句中,而是来到了下面这行代码处。

在这里插入图片描述

程序运行到这里,你会不会有一个大大的疑问,那就是我们的beanFactory不是还没创建么,怎么在这儿又开始调用方法了呢,难道是已经创建了吗?

我们向上翻阅GenericApplicationContext类的代码,发现原来是在这个类的无参构造方法里面,就已经实例化了beanFactory这个对象。也就是说,在创建GenericApplicationContext对象时,无参构造器里面就new出来了beanFactory这个对象。

在这里插入图片描述

相当于我们做了非常核心的一步,即创建了一个beanFactory对象,而且该对象还是DefaultListableBeanFactory类型的。

现在,我们已经知道了在GenericApplicationContext这个类的无参构造方法里面,就已经实例化了beanFactory这个对象。那么,你可能会有疑问,究竟是在什么地方调用GenericApplicationContext类的无参构造方法的呢?

这时,我们可以去看一下我们的单元测试类(例如IOCTest_Ext),如下图所示。

在这里插入图片描述

只要点进去AnnotationConfigApplicationContext类里面去看一看,你就知道大概了,如下图所示,原来AnnotationConfigApplicationContext类继承了GenericApplicationContext这个类,所以,当我们实例化AnnotationConfigApplicationContext时就会调用其父类的构造方法,相应地这时就会对我们的BeanFactory进行实例化了。

在这里插入图片描述

好了,我们不扯远了,还是回到主题。BeanFactory对象创建好了之后,接下来就是要给其设置一个序列化id,相当于打了一个id标识。我们不妨Inspect一下getId方法的值,发现它是org.springframework.context.annotation.AnnotationConfigApplicationContext@51e2adc7这么一长串的字符串,原来这个序列化id就是它啊!

在这里插入图片描述

按下F6快捷键让程序往下运行,直至程序运行到下面这行代码处,refreshBeanFactory方法就执行完了。

在这里插入图片描述

该方法所做的事情很简单,无非就是创建了一个BeanFactory对象(DefaultListableBeanFactory类型的),并为其设置好了一个序列化id

getBeanFactory():返回设置了序列化id后的BeanFactory对象

接下来,我们就要看看getBeanFactory方法了。按下F5快捷键进入该方法里面,如下图所示,发现它里面就只是做了一件事,即返回设置了序列化id后的BeanFactory对象。

在这里插入图片描述

按下F6快捷键让程序往下运行,还是运行到下面这行代码处,可以看到这儿是用ConfigurationListableBeanFactory接口去接受我们刚刚实例化的BeanFactory对象(DefaultListableBeanFactory类型的)。

在这里插入图片描述

继续按下F6快捷键让程序往下运行,一直让程序运行到下面这行代码处。程序运行至此,就返回了我们刚刚创建好的那个BeanFactory对象,只不过这个BeanFactory对象,由于我们刚创建,所以它里面的什么东西都是默认的一些设置。

在这里插入图片描述

至此,我们就分析完了obtainFreshBeanFactory方法,以上就是该方法所做的事情,即获取BeanFactory对象。

prepareBeanFactory(beanFactory):BeanFactory的预准备工作,即对BeanFactory进性一些预处理

接下来,我们就得来说道说道prepareBeanFactory方法了。顾名思义,该方法就是对BeanFactory做一些预处理,即BeanFactory的预准备工作。

为什么要在这儿对BeanFactory做一些预处理啊?因为我们前面刚刚创建好的BeanFactory还没有做任何设置呢,所以就得在这儿对BeanFactory做一些设置了。那到底做了哪些设置呢?下面我们就得进入prepareBeanFactory方法里面一探究竟了。

按下F5快捷键进入该方法中,如下图所示,我们发现会对BeanFactory进行一系列的赋值(即设置一些属性)。比方说,设置BeanFactory的类加载器,就得像下面这样。

在这里插入图片描述

以及,设置支持相关表达式语言的解析器。

在这里插入图片描述

还有,添加属性的编辑注册器等等,总之,会对BeanFactory设置非常多的东西。

在这里插入图片描述

继续向下看prepareBeanFactory方法,你会发现还向BeanFactory中添加了一个BeanPostProcessor,即ApplicationContextAwareProcessor。

在这里插入图片描述

温馨提示:这儿只是向BeanFactory中添加了部分的BeanPostProcessor,而不是添加所有的,比如我们现在只是向BeanFactory中添加了一个叫ApplicationContextAwareProcessor的BeanPostProcessor。它的作用,我们之前也看过了,就是在bean初始化以后来判断这个bean是不是实现了ApplicationContextAware接口。

继续向下看prepareBeanFactory方法,可以看到现在是来为BeanFactory设置忽略的自动装配的接口,比如说像EnvironmentAware、EmbeddedValueResolverAware等等这些接口。

在这里插入图片描述

那么,设置忽略的自动装配的这些接口有什么作用呢?作用就是,这些接口的实现类不能通过接口类型来自动注入。

继续向下看prepareBeanFactory方法,可以看到现在是来为BeanFactory注册可以解析的自动装配。

在这里插入图片描述

所谓的可以解析的自动装配,就是说,我们可以直接在任何组件里面自动注入像BeanFactory、ResourceLoader、ApplicationEventPublisher(它就是上一讲我们讲述的事件派发器)以及ApplicationContext(也就是我们的IOC容器)这些东西。

继续向下看prepareBeanFactory方法,可以看到现在又向BeanFactory中添加了一个BeanPostProcessor,只不过现在添加的是一个叫ApplicationListenerDetector的BeanPostProcessor。

在这里插入图片描述

也就是说,会向BeanFactory中添加很多的后置处理器,后置处理器的作用就是在bean初始化前后做一些工作。

继续向下看prepareBeanFactory方法,可以看到有一个if判断语句,它这是向BeanFactory中添加编译时与AspectJ支持相关的东西。

在这里插入图片描述

而我们现在默认的这些都是运行时的动态代理,所以你会看到这样一个现象,按下F6快捷键让程序往下运行,程序并不会进入到以上if判断语句中,而是来到了下面这个if判断语句处。

在这里插入图片描述

这儿是在向BeanFactory中注册一些与环境变量相关的bean,比如注册了一个名字是environment,值是当前环境对象(其类型是ConfigurableEnvironment)的bean。

在这里插入图片描述

除此之外,还注册了一个名字为systemProperties的bean,也即系统属性,它是通过当前环境对象的getSystemProperties方法获得的。我们来看一下系统属性是个什么东西,进入getSystemProperties方法里面,如下图所示,可以看到系统属性就是一个Map<String, Object>,该Map里面的key/value就是环境变量里面的key/value。

在这里插入图片描述

最后,还会注册一个名字为systemEnvironment的bean,即系统的整个环境信息。我们也不妨点进去getSystemEnvironment方法里面去看一下,如下图所示,发现系统的整个环境信息也是一个Map<String, Object>

在这里插入图片描述

也就是说,我们向BeanFactory中注册了以上三个与环境变量相关的bean。以后,如果我们想用的话,只须将它们自动注入即可。

继续按下F6快捷键让程序往下运行,一直让程序运行到下面这行代码处。程序运行至此,说明prepareBeanFactory方法就执行完了,相应地,BeanFactory就已经创建好了,里面该设置的属性也都设置了。

在这里插入图片描述

postProcessBeanFactory(beanFactory):BeanFactory准备工作完成后进行的后置处理工作

接下来,我们就得来说道说道postProcessBeanFactory方法了。它说的就是在BeanFactory准备工作完成之后进行的后置处理工作。我们不妨点进去该方法里面看看,它究竟做了哪些事,如下图所示,发现它里面是空的。

在这里插入图片描述

这不是和我们刷新容器前的预处理工作中的initPropertySources方法一样吗?方法里面都是空的,默认都是不进行任何处理的,但是方法都是protected类型的,这也就是说子类可以通过重写这个方法,在BeanFactory创建并预处理完成以后做进一步的设置。

这个方法只有在子类重写的时候有用,只不过现在它还是空的,里面啥也没做。

继续按下F6快捷键让程序往下运行,一直让程序运行到下面这行代码处。程序运行到这里之后,我们先让它停一停。

在这里插入图片描述

至此,BeanFactory的创建以及预准备工作就已经完成啦😂

既然有了BeanFactory对象,那么接下来我们就要利用BeanFactory来创建各种组件了。在下一讲,我们就来看看后续的流程。

转载地址:https://liayun.blog.csdn.net/article/details/113995147 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:Spring注解驱动开发第42讲——Spring IOC容器创建源码解析(二)之执行BeanFactoryPostProcessor
下一篇:Spring注解驱动开发第40讲——你晓得@EventListener这个注解的原理吗?

发表评论

最新留言

第一次来,支持一个
[***.219.124.196]2024年04月21日 22时21分10秒