IOC 容器的初始化过程分为三步骤:
Resource 定位
BeanDefinition 的载入和解析
BeanDefinition 注册
Resource 定位。我们一般用外部资源来描述 Bean 对象,所以在初始化 IOC 容器的第一步就是需要定位这个外部资源。
BeanDefinition 的载入和解析。装载就是 BeanDefinition 的载入。BeanDefinitionReader 读取、解析 Resource 资源,也就是将用户定义的 Bean 表示成 IOC 容器的内部数据结构:BeanDefinition。在 IOC 容器内部维护着一个 BeanDefinition Map 的数据结构,在配置文件中每一个都对应着一个BeanDefinition对象。
BeanDefinition 注册。向IOC容器注册在第二步解析好的 BeanDefinition,这个过程是通过 BeanDefinitionRegistery 接口来实现的。在 IOC 容器内部其实是将第二个过程解析得到的 BeanDefinition 注入到一个 HashMap 容器中,IOC 容器就是通过这个 HashMap 来维护这些 BeanDefinition 的。在这里需要注意的一点是这个过程并没有完成依赖注入,依赖注册是发生在应用第一次调用 getBean() 向容器索要 Bean 时。当然我们可以通过设置预处理,即对某个 Bean 设置 lazyinit 属性,那么这个 Bean 的依赖注入就会在容器初始化的时候完成。
// 根据 Xml 配置文件创建 Resource 资源对象。ClassPathResource 是 Resource 接口的子类,
//bean.xml 文件中的内容是我们定义的 Bean 信息。
ClassPathResource resource = new ClassPathResource("bean.xml");
//创建一个 BeanFactory。DefaultListableBeanFactory 是 BeanFactory 的一个子类,BeanFactory //作为一个接口,其实它本身是不具有独立使用的功能的,而 DefaultListableBeanFactory 则是真正可以独立
//使用的 IOC 容器,它是整个 Spring IOC 的始祖,在后续会有专门的文章来分析它。
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
//创建 XmlBeanDefinitionReader 读取器,用于载入 BeanDefinition 。
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
//开启 Bean 的载入和注册进程,完成后的 Bean 放置在 IOC 容器中。
reader.loadBeanDefinitions(resource);
Resource 定位
Spring 为了解决资源定位的问题,提供了两个接口:Resource、ResourceLoader
其中 Resource 接口是 Spring 统一资源的抽象接口
ResourceLoader 则是 Spring 资源加载的统一抽象。
在上面那段代码中 new ClassPathResource("bean.xml") 为我们定义了资源
那么 ResourceLoader 则是在什么时候初始化的呢?
看 XmlBeanDefinitionReader 构造方法:
直接调用父类 AbstractBeanDefinitionReader :
核心在于设置 resourceLoader 这段,如果设置了 ResourceLoader 则用设置的,
否则使用 PathMatchingResourcePatternResolver ,该类是一个集大成者的 ResourceLoader。
BeanDefinition 的载入和解析
reader.loadBeanDefinitions(resource); 开启 BeanDefinition 的解析过程。如下:
而将 Resource 封装成 EncodedResource 主要是为了对 Resource 进行编码,保证内容读取的正确性
从 encodedResource 源中获取 xml 的解析源,调用 doLoadBeanDefinitions() 执行具体的解析过程
在该方法中主要做两件事:
1、根据 xml 解析源获取相应的 Document 对象,
2、调用 registerBeanDefinitions() 开启 BeanDefinition 的解析注册过程。
转换为 Document 对象
调用 doLoadDocument() 会将 Bean 定义的资源转换为 Document 对象。
loadDocument() 方法接受五个参数:
inputSource:加载 Document 的 Resource 源
entityResolver:解析文件的解析器
errorHandler:处理加载 Document 对象的过程的错误
validationMode:验证模式
namespaceAware:命名空间支持。如果要提供对 XML 名称空间的支持,则为true
就已经将定义的 Bean 资源文件,载入并转换为 Document 对象了,那么下一步就是如何将其解析为 Spring IOC 管理的 Bean 对象并将其注册到容器中。
这个过程有方法 registerBeanDefinitions() 实现。如下:
首先创建 BeanDefinition 的解析器 BeanDefinitionDocumentReader,
然后调用 documentReader.registerBeanDefinitions() 开启解析过程,这里使用的是委派模式,具体的实现由子类 DefaultBeanDefinitionDocumentReader 完成。
对 Document 对象的解析
从 Document 对象中获取根元素 root,然后调用 doRegisterBeanDefinitions() 开启真正的解析过程
preProcessXml()、postProcessXml() 为前置、后置增强处理,目前 Spring 中都是空实现,
parseBeanDefinitions() 是对根元素 root 的解析注册过程
迭代 root 元素的所有子节点,对其进行判断,若节点为默认命名空间,则ID调用 parseDefaultElement() 开启默认标签的解析注册过程,
否则调用 parseCustomElement() 开启自定义标签的解析注册过程。
标签解析
若定义的元素节点使用的是 Spring 默认命名空间,则调用 parseDefaultElement() 进行默认标签解析,如下:
对四大标签:import、alias、bean、beans 进行解析,其中 bean 标签的解析为核心工作
对于默认标签则由 parseCustomElement() 负责解析。
注册 BeanDefinition
调用 BeanDefinitionReaderUtils.registerBeanDefinition() 注册,
其实这里面也是调用 BeanDefinitionRegistry 的 registerBeanDefinition()来注册 BeanDefinition ,
不过最终的实现是在 DefaultListableBeanFactory 中实现,如下:
最核心的部分是这句 this.beanDefinitionMap.put(beanName, beanDefinition) ,
所以注册过程也不是那么的高大上,就是利用一个 Map 的集合对象来存放,key 是 beanName,value 是 BeanDefinition。