Spring源码解析--《SPRING技术内幕:深入解析Spring架构与设计原理》读书笔记(一):IOC容器初始化过程

通过阅读相关章节内容,Spring中IOC容器的加载中,我们需要了解下列几个概念:

  • Resource:是一个定位、访问资源的抽象接口,包含了多种资源操作的基础方法定义,如getInputStream()exists()isOpen()getDescription()等。
  • BeanDefinition:POJO对象在IOC容器中的抽象,通过此数据结构,使IOC容器能方便地对POJO对象进行管理,其中可以设置Bean的一些属性,如:scopebeanClassNamelazyInitdependentsOn等。
  • BeanFactory:基础容器定义接口,Spring IOC 容器的一个最基础的行为定义的接口,定义了一个IOC容器所需要的最基础的行为规范,包括:getBean()等。
  • ApplicationContext:高级容器定义接口,在BeanFactory的基础上,添加了一些扩展功能,如,ResoruceLoaderMessageSourceApplicationEventPublisher等。

一、以编程的方式使用IOC容器

 ClassPathResource res = new ClassPathResource("bean.xml");
 DefaultListableBeanFactory = new DefaultListableBeanFactory();
 XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader();
 reader.loadBeanDefinition(res);

从上面的代码清单可看出,编程方式使用一个IOC容器的步骤主要如下:

  • 创建IOC配置文件的抽象资源,包含对BeanDefinition的定义
  • 创建BeanFactory
  • 创建一个载入BeanDefinition的读取器,通过一个回调配置给BeanFactory
  • 从定义好的资源位置读取配置信息,具体解析过程由DefinitionReader来完成。

完成这些步骤后,就可以直接使用IOC容器了。

二、ApplicationContext容器设计原理

应用上下文的主要功能都在Abstract__ApplicationContext基类中实现了,而一个具体的应用上下文只需要实现和它自身设计相关的两个功能:
1、启动IOC容器的refresh()(通常在构造函数中)过程。
2、从文件系统中加载Bean的定义资源,使用getResourceByPath()得到资源定位,如FileSystemXmlApplicationContext中使用getResourceByPath获取一个FileSystemResource

三、IOC容器初始化过程

refresh()启动,整个过程可以拆分为三个部分:

  • BeanDefinition的Resource定位
  • BeanDefinition的Resource载入
  • BeanDefinition的Resource注册
    下述过程以FileSystemApplicationContext为例,分析IOC容器的初始化过程。

1、 Resource定位

ResourceLoader通过统一的Resource接口完成,对各种形式的BeanDefinition提供了同一接口。此过程类似于容器寻找数据的过程
资源定位的整个过程可总结为:


  • FileSystemXmlApplicationContext构造函数调用AbstractApplicationContext中的refresh()方法;
  • refresh中调用AbstractApplicationContext中的obtainFreshBeanFactory()方法;
  • obtainFreshBeanFactory中调用AbstractRefreshableApplicationContext中的refreshBeanFactory()方法;
  • refreshBeanFactory()中包含下述几步操作:

  • 判断若已经建立了BeanFactory,则销毁并关闭它;
  • 通过createBeanFactory()创建一个DefaultListableBeanFactory,调用AbstractRefreshableApplicationContext中的loadBeanDefinitions(DefaultListableBeanFactory beanFactory),此为一抽象方法,用于将BeanDefinition装入BeanFactory中,其具体实现委托给一个或多个bean definition reader
  • AbstractBeanDefinitionReader中的一种重载方法loadBeanDefinitions(String location, Set<Resource> actualResources),其通过判断ResourceLoader类型,使用不同的getResource()方法获取Resource
  • DefaultResourceLoader中的getResource()为例,其中根据传入的loacation字符串的格式进行分别处理:1、若以”/”开头,作为相对路径处理2、若以”classpath:”开头,作为类路径处理3、若为URL资源路径,作为URL资源路径处理4、若都不是,则委托给子类的getResourceByPath(location)方法实现。其中上述所有处理都是对path进行解析,返回一个Resource对象;

定位完成后为BeanDefinition的载入创造了I/O操作的条件,但是数据还没有开始读入。

2、BeanDefinition载入

把用户定义好的Bean表示成IOC容器内部的数据结构,即BeanDefinition。下面,以DefaultListableBeanFactory为例,分析IOC容器完成BeanDefinition载入的过程。
载入过程图解
BeanDefinition载入的过程可分为两个部分:通过XML解析器得到document对象按照Spring的bean规则解析document对象

1)通过XML解析器得到document对象

  • refresh()方法详细地描述了整个ApplicationContext的初始化过程,如BeanFactory的更新MessageSource和PostProcessor的注册等。
    refresh方法详情
  • createBeanFactory()方法中调用的loadBeanDefinitions(DefaultListableBeanFactory beanFactory)方法启动对BeanDefinition的载入,它是一个抽象方法,用于将BeanDefinition装入BeanFactory中,其具体实现委托给一个或多个bean definition reader
  • 实际载入过程在AbstractXmlApplicationContext中实现:

    • 初始化读取器XmlBeanDefinitionReader
    • 然后通过回调方式将读取器在IOC容器中设置好
    • 启动读取器完成BeanDefinition在IOC容器中的载入
      载入过程实现
  • 上面过程调用的loadBeanDefinitions(beanDefinitionReader)具体实现内容如下:

    • 获取BeanDefinition信息的Resource定位
    • 调用XmlBeanDefinitionReader读取,具体载入过程委托给BeanDefinitionReader完成
    • 使用XmlBeanDefinitionReader载入BeanDefinition到容器中
      载入过程具体实现
    • reader.loadBeanDefinitions中开始进行BeanDefinition的载入,此时,其XmlBeanDefinitionReader父类AbstractbeanDefinitionReader已经为BeanDefinition的载入做好了准备:
      父类准备加载BeanDefinition
    • 上面的`loadBeanDefinitions(Resource resource)是一个接口方法,具体实现在XmlBeanDefinitionReader中:

    • 得到代表XML文件的Resource,其中封装了对XML文件的I/O操作,使读取器可以在打开I/O流后得到XML文件对象
      载入XML形式的BeanDefinition
      准备读取I/O的InputSource
    • 按照Spring的Bean定义规则打开这个XML文档树进行解析,解析过程交给BeanDefinitionParserDelegate来完成
      具体读取过程
      ps: 获取Document对象的过程在DefaultDocumentLoader中实现。

ps:
上述过程使用的是XML方式定义的BeanDefinition,故使用XmlBeanDefinitionReader,若使用其他方式的BeanDefinition,则需要使用对应种类的BeanDefinitionReader来完成载入工作

2)按照Spring的bean规则解析document对象

Spring的BeanDefinition按照Spring的Bean语义要求进行解析并转化为容器内部数据结构的过程(同时包含对载入的Bean数量进行统计)由registerBeanDefinitions(doc, resource)完成。
registerBeanDefinitions代码清单


  • 使用documentReader按照Spring的Bean规则document对象,此处使用默认设置的DefaultBeanDefinitionDocumentReader进行解析
    DefaultBeanDefinitionDocumentReader代码清单
    ps:下述过程由本人参照4.3.11源码进行总结,与原书中有差异,感兴趣的读者可自行获取4.3.11源码进行验证。


  • registerBeanDefinitions(Document doc, XmlReaderContext readerContext)中调用doRegisterBeanDefinitions(Element root)根据root下的element注册bean
    doRegisterbeanDefinitions代码清单
  • 调用parseBeanDefinitions(root, this.delegate)解析document中root级别目录要素:"import""alias""bean"
    解析element
    ps:此处我们着重看默认元素中<bean>元素的解析过程。其他元素解析感兴趣的同学可以自行查阅spring4.3.11源码中DefaultBeanDefinitionDocumentReader内容


  • BeanDefinitionParserDelegate中的parseCustomElement(Element ele)解析自定义元素,此处就不做详细跟踪研究了,感兴趣的看官可以自行去源码中研究
  • DefaultBeanDefinitionDocumentReader中的parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate)解析默认元素
    解析默认元素

  • processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate)完成BeanDefinition的处理,并将处理结果交由BeanDefinitionHolder对象持有,BeanDefinitionHolder对象还持有其他与BeanDefinition对象使用相关的信息,如:Bean的名字别名集合
    解析bean元素
  • BeanDefinitionParserDelegate中的parseBeanDefinitionElement(Element ele, BeanDefinition containingBean)完成具体的Spring BeanDefinition解析
    bean元素具体解析过程
  • parseBeanDefinitionElement(
    Element ele, String beanName, BeanDefinition containingBean)
    对BeanDefinition中定义的元素进行详细处理,如:attribute值处理
    bean中定义元素详细处理
    ps:这里我们跟踪一下bean中property属性的解析处理,从而了解解析元素的具体操作
  • parsePropertyElements(Element beanEle, BeanDefinition bd)获取bean下property元素并启动解析过程
    获取property元素并启动解析
  • parsePropertyElement(Element ele, BeanDefinition bd)开启value(结果由PropertyValue持有)和meta的详细解析,并将解析结果转载到BeanDefinition中
    这里写图片描述
  • parsePropertyValue(Element ele, BeanDefinition bd, String propertyName)解析property元素的值:value、ref、子元素
    详细解析property属性
  • parsePropertySubElement(Element ele, BeanDefinition bd, String defaultValueType)解析property下的子元素
    解析property下子元素-1
    解析property下子元素-2
  • 这里以解析list为例,看看对于property下的子元素的解析过程
    List解析过程
    其中具体的List中元素的解析如下
    List中元素解析

至此,xml文件中定义的BeanDefinition就被整个载入到IOC容器中,并在容器中建立了数据映射,即将POJO抽象到了IOC容器中。从而,可以以AbstractBeanDefinition为入口,让IOC容器执行索引查询操作。以上工作使IOC容器大致完成了管理Bean对象的数据准备工作(初始化过程)。由于依赖注入在此时还没有发生,在IOC的BeanDefinition中存在的还只是一些静态配置信息,若要完全发挥容器作用,需要完成下述数据向容器注册的过程

3、向IOC容器注册BeanDefinition

通过调用DefaultListableBeanFactory中实现BeanDefinitionRegistry接口的registerBeanDefinition(String beanName, BeanDefinition beanDefinition)方法完成。此过程把载入过程中解析得到的BeanDefinition向IOC容器进行注册。
__ps:__IOC容器内部将BeanDefinition注入到DefaultListableBeanFactory中创建的一个hashMap中:

/** Map of singleton and non-singleton bean names, keyed by dependency type */
    private final Map<Class<?>, String[]> allBeanNamesByType = new ConcurrentHashMap<Class<?>, String[]>(64);

注册调用过程

其中注册的具体实现如下:
+ 完整的代码清单我这里就不截取了,只放几个关键点的代码截图

  • 检查ioc容器中是否已有同名BeanDefiniton注册:
    注册查重
  • 正常注册一个bean的过程:
    正常注册bean的过程

至此,IOC容器初始化的过程就全部完成了,此时,在使用的IOC容器DefinitionBeanFactory中已经建立了整个Bean的配置信息,而且这些BeanDefinition已经可以被容器使用,他们都在beanDefinitionmap里被检索使用。容器的作用就是对这些信息进行处理和维护。这些信息是容器建立依赖反转的基础
注意:此处所说的IOC容器初始化过程中不包括Bean依赖注入的实现。在Spring IOC的设计中,Bean的定义载入和依赖注入是两个独立的过程。依赖注入一般发生在应用第一次通过getBean向容器索取Bean的时候。但是若配置了lazyinit属性,Bean的依赖注入在IOC容器初始化时就完成了

猜你喜欢

转载自blog.csdn.net/yzy199391/article/details/80305831