本文共 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 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!