IOC容器的初始化(一)

IOC 容器的初始化包括 BeanDefinition 的 Resource 定位、载入和注册这三个基本的过程。我们以 ApplicationContext 为例讲解,ApplicationContext 系列容器也许是我们最熟悉的,因为 Web 项 目 中 使 用 的 XmlWebApplicationContext 就 属 于 这 个 继 承 体系 , 还 有ClasspathXmlApplicationContext 等,其继承体系如下图所示:


ApplicationContext 允许上下文嵌套,通过保持父上下文可以维持一个上下文体系。对于 Bean 的查找可以在这个上下文体系中发生,首先检查当前上下文,其次是父上下文,逐级向上,这样为不同的Spring 应用提供了一个共享的 Bean 定义环境。

下面我们分别简单地演示一下两种 IOC 容器的创建过程

1、FileSystemXmlApplicationContext IOC 容器流程

(1)、ApplicationContext = new FileSystemXmlApplicationContext(xmlPath);
先看其构造函数的调用:

实际调用的构造函数:


(2)、设置资源加载器和资源定位

通过分析在创建FileSystemXmlApplicationContext容器时,构造方法做以下两项重要工作:首先,调用父类容器的构造方法(super(parent)方法)为容器设置好Bean资源加载器。然后,再调用父类AbstractRefreshableConfigApplicationContext的setConfigLocations(configLocations)方法设置Bean定义资源文件的定位路径。通过追踪FileSystemXmlApplicationContext的继承体系,发现其父类的父类

AbstractApplicationContext中初始化IOC容器所做的主要源码如下:



AbstractApplicationContext 构造方法中调用 PathMatchingResourcePatternResolver 的构造

方法创建 Spring 资源加载器 。


接下来FileSystemXmlApplicationContext执行setConfigLocations方法通过调用其父类AbstractRefreshableConfigApplicationContext的方法进行对Bean定义资源文件的定位,该方法的源码如下:



通过这两个方法的源码我们可以看出,我们既可以使用一个字符串来配置多个 Spring Bean 定义资源文件,也可以使用字符串数组,即下面两种方式都是可以的:
ClasspathResource res = new ClasspathResource(“a.xml,b.xml,……”);多个资源文件路径之间可以是用” , ; \t\n”等分隔。
ClasspathResource res = new ClasspathResource(newString[]{“test1.xml”,”test2.xml”,……});

至此,SpringIOC 容器在初始化时将配置的 Bean 定义资源文件定位为 Spring 封装的 Resource。

(3)、AbstractApplicationContext 的 refresh 函数载入 Bean 定义过程:
SpringIOC 容器对 Bean 定义资源的载入是从 refresh()函数开始的,refresh()是一个模板方法,refresh()方法的作用是:在创建 IOC 容器前,如果已经有容器存在,则需要把已有的容器销毁和关闭,
以保证在 refresh 之后使用的是新建立起来的 IOC 容器。refresh 的作用类似于对 IOC 容器的重启,在新建立好的容器中对容器进行初始化,对 Bean 定义资源进行载入
FileSystemXmlApplicationContext 通过调用其父类 AbstractApplicationContext 的 refresh()
函数启动整个 IOC 容器对 Bean 定义的载入过程:



refresh()方法主要为IOC容器Bean的生命周期管理提供条件,IOC载入Bean定义资源文件从其子类容器的refreshBeanFactory()启动,所以整个refresh()中ConfigurableListableBeanFactorybeanFactory=obtainFreshBeanFactory();这句以后代码的都是注册容器的信息源和生命周期事件,载入过程就是从这句代码启动。refresh()方法的作用是:在创建IOC容器前,如果已经有容器存在,则需要把已有的容器销毁和关闭,以保证在refresh之后使用的是新建立起来的IOC容器。refresh的作用类似于对IOC容器的重启,在新建立好的容器中对容器进行初始化,对Bean定义资源进行载入

(4)、AbstractApplicationContext的obtainFreshBeanFactory()方法调用子类容器的


AbstractApplicationContext类中只抽象定义了refreshBeanFactory()方法,容器真正调用的是其子类AbstractRefreshableApplicationContext实现的refreshBeanFactory()方法,

方法的源码如下:



在这个方法中,先判断BeanFactory是否存在,如果存在则先销毁beans并关闭beanFactory,接着创建DefaultListableBeanFactory,并调用loadBeanDefinitions(beanFactory)装载bean定义。

(5)、AbstractRefreshableApplicationContext子类的loadBeanDefinitions方法:
AbstractRefreshableApplicationContext中只定义了抽象的loadBeanDefinitions方法,容器真正调用的是其子类AbstractXmlApplicationContext对该方法的实现,AbstractXmlApplicationContext的主要源码如下:loadBeanDefinitions方法同样是抽象方法,是由其子类实现的,也即在AbstractXmlApplicationContext中。





XmlBean读取器(XmlBeanDefinitionReader)调用其父类AbstractBeanDefinitionReader的reader.loadBeanDefinitions方法读取Bean定义资源。由于我们使用FileSystemXmlApplicationContext作为例子分析,因此getConfigResources的返回值为null,因此程序执行reader.loadBeanDefinitions(configLocations)分支。

(6)、AbstractBeanDefinitionReader读取Bean定义资源,在其抽象父类AbstractBeanDefinitionReader中定义了载入过程。
AbstractBeanDefinitionReader的loadBeanDefinitions方法源码如下:




loadBeanDefinitions(Resource...resources)方法调用XmlBeanDefinitionReader 的 loadBeanDefinitions 方法。从对 AbstractBeanDefinitionReader 的 loadBeanDefinitions 方法源码分析可以看出该方法做了以下两件事:首先,调用资源加载器的获取资源方法 resourceLoader.getResource(location),获取到要加载的资源。其次,真正执行加载功能是其子类 XmlBeanDefinitionReader 的 loadBeanDefinitions 方法。



看到上面的ResourceLoader与ApplicationContext的继承系图,可以知道其实际调用的是DefaultResourceLoader中的getSource()方法定位Resource,因为FileSystemXmlApplicationContext本身就是DefaultResourceLoader的实现类,所以此时又回到了FileSystemXmlApplicationContext中来。

(7)、资源加载器获取要读入的资源:

XmlBeanDefinitionReader通过调用其父类DefaultResourceLoader的getResource方法获取要加载的资源,其源码如下


如果不是classPath路径也不是Url方式,那么使用文件系统资源对象来定义bean文件


这样代码就回到了FileSystemXmlApplicationContext中来,他提供了FileSystemResource来完成从文件系统得到配置文件的资源定义。
这样,就可以从文件系统路径上对IOC配置文件进行加载,当然我们可以按照这个逻辑从任何地方加载,在Spring中我们看到它提供的各种资源抽象,比如

ClassPathResource,URLResource,FileSystemResource等来供我们使用。上面我们看到的是定位Resource的一个过程,而这只是加载过程的一部分.

(8)、XmlBeanDefinitionReader加载Bean定义资源:

继续回到XmlBeanDefinitionReader的loadBeanDefinitions(Resource…)方法看到代表bean文件的资源定义以后的载入过程



通过源码分析,载入Bean定义资源文件的最后一步是将Bean定义资源转换为Document对象,该过程由documentLoader实现

(9)、DocumentLoader将Bean定义资源转换为Document对象:

DocumentLoader将Bean定义资源转换成Document对象的源码如下:


该解析过程调用JavaEE标准的JAXP标准进行处理。至此SpringIOC容器根据定位的Bean定义资源文件,将其加载读入并转换成为Document对象过程完成。接下来我们要继续分析SpringIOC容器将载入的Bean定义资源文件转换为Document对象之

后,是如何将其解析为SpringIOC管理的Bean对象并将其注册到容器中的。

(10)、XmlBeanDefinitionReader解析载入的Bean定义资源文件:

XmlBeanDefinitionReader类中的doLoadBeanDefinitions方法是从特定XML文件中实际载入Bean定义资源的方法,该方法在载入Bean定义资源之后将其转换为Document对象,接下来调用registerBeanDefinitions启动SpringIOC容器对Bean定义的解析过程。
registerBeanDefinitions方法源码如下:


Bean定义资源的载入解析分为以下两个过程:
首先,通过调用XML解析器将Bean定义资源文件转换得到Document对象,但是这些Document对象并没有按照Spring的Bean规则进行解析。这一步是载入的过程

其次,在完成通用的XML解析之后,按照Spring的Bean规则对Document对象进行解析。按照Spring的Bean规则对Document对象解析的过程是在接口BeanDefinitionDocumentReader的实现类DefaultBeanDefinitionDocumentReader中实现的。

(11)、DefaultBeanDefinitionDocumentReader对Bean定义的Document对象解析:
BeanDefinitionDocumentReader通过registerBeanDefinitions调用其实现类DefaultBeanDefinitionDocumentReader对Document对象进行解析,解析的代码如下:




通过上述SpringIOC容器对载入的Bean定义Document解析可以看出,我们使用Spring时,在Spring配置文件中可以使用<import>元素来导入IOC容器所需要的其他资源,SpringIOC容器在解析时会首先将指定导入的资源加载进容器中。使用<ailas>别名时,SpringIOC容器首先将别名元素所定义的别名注册到容器中。对于既不是<import>元素,又不是<alias>元素的元素,即Spring配置文件中普通的<bean>元素的解析由BeanDefinitionParserDelegate类的parseBeanDefinitionElement方法来实现。 

下一篇:Spring IOC容器的初始化(二)

猜你喜欢

转载自blog.csdn.net/m0_37444820/article/details/80790900