Spring解析之IoC:XML配置文件的加载及BeanFactory的创建

前言
本文是Spring源码解析IoC部分的第一篇文章,以最简单的IoC案例作为切入点,主要分析了XML配置文件是如何被加载的,Bean工厂是如何创建的

为了分析方便,本文创建了一个普通java类Student,有String类型nameint类型的age两个属性,此外还有一个公共无参,无返回值的void study()方法,初学时不管有多少种加载配置文件的方法new ClassPathXmlApplicationContext(String)必然是最常用的一种。我们在resources目录下创建了名为beans.xml的Spring配置文件

图1. 初始化beans.xml配置

在配置文件中使用 表明将 Student交给Spring来管理,这里需要插一嘴,IoC最主要的作用是将繁琐的创建对象的过程与代码需要真正表达的业务逻辑解耦,所谓的创建对象过程还要分为两部分:1.构建出对象实体;2.建立对象间依赖关系,其中第二部分主要通过依赖注入 DI实现,所以说很多书上会将 DI作为 IoC实现的手段。完成配置后我们就可以通过一段简单的代码得到托管的 Student,并调用其中的 study()
图2. 从Spring容器中得到托管对象

既然第二句已经获取被托管的对象,那么 IoC的主流程必然存在于第一句中,我们就以 new ClassPathXmlApplicationContext(String)为入口,看看Spring是如何完成控制反转的
Spring解析之IoC:XML配置文件的加载及BeanFactory的创建_第1张图片
图3. ClassPathXmlApplicationContext构造器

图中最后一个构造器才是逻辑真正的开始位置,第一个参数为配置文件数组,第二个参数表示是否需要刷新Spring上下文标识,这里为true,第三个参数为当前 context的父上下文。在进一步深入之前我需要先给出 ClassPathXmlApplicationContext的类图,类图就像指南针,当我们深入细节迷失时会引导我们识别正确的道路
Spring解析之IoC:XML配置文件的加载及BeanFactory的创建_第2张图片
图4. ClassPathXmlApplicationContext类图

图3中 setConfigLocations(configLocations)主要作用是解析 ClassPathXmlApplicationContext(String)参数中的配置文件路径,并将配置的单个或者多个配置文件存放在父类 AbstractRefreshableConfigApplicationContext的成员变量 configLocations数组内。如果路径中存在 ${}占位符,会用正确的值进行替换,由于其处理过程和使用 引入外部配置文件处理流程一致,所有将在后面解析 原理时单独讲解。设置完配置文件后调用 refresh()刷新整个Spring上下文信息
Spring解析之IoC:XML配置文件的加载及BeanFactory的创建_第3张图片
图5. AbstractApplicationContext的refresh()

该方法包裹在同步代码块中,防止整个Spring上下文刷新时多个线程造成的冲突,在查看对象监视器 startupShutdownMonitor的使用时发现,在同类的 close()中也使用了该对象监视器的同步代码块,这也引出了另一层含义,刷新Spring上下文相当于“创建”,而 close()相当于”销毁”,共享同一个对象监视器保证了对Spring上下文的两种“侵入性”操作不能共存。此外该方法在 AbstractApplicationContext中,很明显的模板方法设计模式, prepareRefresh()进行刷新前的准备工作
Spring解析之IoC:XML配置文件的加载及BeanFactory的创建_第4张图片
图6. AbstractApplicationContext的prepareRefresh()

方法中 initPropertySources()为空实现不用理会, getEnvironment()得到一个”标准环境对象” StandardEnvironment,而 validateRequiredProperties()负责校验加载时配置文件或者系统参数中存在必须配置但没有配置的情况,所有必须配置的属性存储在 AbstractPropertyResolver中的成员变量 Set requiredProperties中,如果检测到需要配置而未配置的属性抛出 MissingRequiredPropertiesException。接着看图5中 obtainFreshBeanFactory()
Spring解析之IoC:XML配置文件的加载及BeanFactory的创建_第5张图片
图7. AbstractApplicationContext的obtainFreshBeanFactory()

该方法顾名思义就是获得新的 BeanFactory,总体实现思路超级简单:1.创建 BeanFactory,由 refreshBeanFactory()完成;2.取出新的 BeanFactory返回,由 getBeanFactory()完成。先看看第一步
Spring解析之IoC:XML配置文件的加载及BeanFactory的创建_第6张图片
图8. AbstractRefreshableApplicationContext的refreshBeanFactory()

首先根据 hasBeanFactory()判断成员变量 beanFactory是否为null,该方法由 beanFactoryMonitor对象监视器的同步块包裹,如果已经存在bean工厂先进行销毁,之后调用 createBeanFactory()创建新的工厂,该方法中 newBeanFactory接口的子类 DefaultListableBeanFactory,该类是bean工厂的对外暴露的核心类,这里同样给出该类的类图,供读者在源码中迷失时指引方向
Spring解析之IoC:XML配置文件的加载及BeanFactory的创建_第7张图片
图9. DefaultListableBeanFactory的类图

我们从图中发现 DefaultListableBeanFactory不仅实现了 BeanFactory接口,同时也实现了 BeanDefinitionRegistry,在开篇中我们曾经说过,对象存放在 BeanFactory中,而“放”这个动作由 BeanRegistry完成,现在两者统一合成到了一个类中。这就像刚学Java面向对象思想的时候老师举过的一个例子,人去关灯这件事,如果要用面向对象的思想表示,”关灯”这个动作应该是封装在灯对象中而不是人对象,随着源码分析的不断深入,我们还会进一步剖析 DefaultListableBeanFactory
创建完后给bean工厂设置唯一标识,该标识和实现 Serializable后生成的 serialVersionUID作用相似,都是保证序列化和反序列化后的对象一致性。
Spring解析之IoC:XML配置文件的加载及BeanFactory的创建_第8张图片
图10. AbstractRefreshableApplicationContext的customizeBeanFactory(DefaultListableBeanFactory)

该方法用于设置 BeanFactory的自定义属性,其实里面就两个: allowBeanDefinitionOverriding,当多个配置文件中存在同一个 id或者 name标识的标签时是否用后加载配置文件中的内容覆盖先加载的,我们可以在创建 ClassPathXmlApplicationContext后自主设置,注释上说设为 false就会发生覆盖,设为 true当出现重复时会抛出异常,但恕我愚笨,无论怎么模拟都只发生了覆盖但不抛出异常,希望读者有知道的给我说说如何模拟; allowCircularReferences表示是否允许循环依赖,说到这个循环依赖我可是吃过它的亏。在第一家公司工作的时候项目组织的很混乱, Service层有时依赖 Dao,有时又图省事 Service之间相互依赖,时不时出现循环依赖问题,造成项目无法启动,关于这种问题如何解决我分享给大家一篇文章 Circular Dependencies in Spring,里面提供了多种解决思路,这里不再赘述。最后一句代码也是非常重要的,看着名字大家十有八九都能猜到它的功能与 @Autowired@Qualifier相关。在Spring中特别喜欢用 xxxResolverxxxHandler给类命名,表示对某个东西的处理类,注解的实现和运行原理也会专门开一篇详述,这里不往下深入。回到图8 loadBeanDefinitions(DefaultListableBeanFactory)封装了读取配置文件并解析的流程
Spring解析之IoC:XML配置文件的加载及BeanFactory的创建_第9张图片
图11. AbstractXmlApplicationContext的loadBeanDefinitions(DefaultListableBeanFactory)

该类的具体实现在父类 AbstractXmlApplicationContext中(如果迷失请看类图),第一句创建了 XmlBeanDefinitionReader的实例,和开篇我们推测的一样,主要功能是将配置文件读取到内存中,之后为其设置“当前运行环境”,其实就是上面设置过的 StandardEnvironmentResourceEntityResolver代表配置文件的解析器,xml的语法定义约束有两种:DTD和XSD,在该解析器的父类 DelegatingEntityResolver中就分别定义了两种特定类型的解析器
图12. DelegatingEntityResolver构造器

此外在初始化 PluggableSchemaResolver中会加载默认 schemas,位置在每个Spring模块的 META-INF/spring.schemas,比如对于 3.2.8.RELEASEspring-beans模块,默认的 schemas如下
Spring解析之IoC:XML配置文件的加载及BeanFactory的创建_第10张图片
图13. 3.2.8.RELEASE版本spring-beans模块的schemas

initBeanDefinitionReader(XmlBeanDefinitionReader)内设置默认解析xml配置文件需要验证,流程走到 loadBeanDefinitions(XmlBeanDefinitionReader)
Spring解析之IoC:XML配置文件的加载及BeanFactory的创建_第11张图片
图14. AbstractXmlApplicationContext的loadBeanDefinition(XmlBeanDefinitionReader)

按照本文的例子启动Spring容器时 configResources为空,所有的配置文件都存放在 configLoactions数组中,当然我们也可以启动Spring容器的时候不传入配置文件,如果不传入的话 getConfigLoactions()会调用子类 XmlWebApplicationContextgetDefaultConfigLocations()返回默认位置 /WEB-INF/applicationContext.xml的配置文件。随后用参数的reader加载配置文件
Spring解析之IoC:XML配置文件的加载及BeanFactory的创建_第12张图片
图15. AbstractBeanDefinitionReader的loadBeanDefinitions(String, Set)

流程走到 XmlBeanDefinitionReader的父类 AbstractBeanDefinitionReader的上图中方法,第一个参数是每一个配置文件,第二个参数为null。这里的 resourceLoader就是 ClassPathXmlApplicationContext的实例,因为该实例实现了 ResourceLoader接口,具体的赋值过程在图11中,流程进入红框处代码,省略中间很多非重点调用,最终创建 BeanDefinitionDocumentReader实例解析xml文件并进行 BeanDefinition的注册
Spring解析之IoC:XML配置文件的加载及BeanFactory的创建_第13张图片
图16. DefaultBeanDefinitionDocumentReader解析xml并注册BeanDefinition入口

标注1是进来的第一步, Document是Spring使用 SAX解析xml之后得到的 Document对象,然后获取xml的根元素对象并传入标注2方法,首先处理 Document中的 profile属性,使用该属性的人可能不多,他就像 maven中的 组合,可以根据运行的不同环境加载不同 profile的值。之后创建一个委派类 BeanDefinitionParserDelegate进行xml文件解析的初始化,其中主要对顶层 内的属性进行解析,在 BeanDefinitionParserDelegate中存储了很多常量,每一个常量都与xml中一个标签或者属性对应,其中就有一组 中的属性
Spring解析之IoC:XML配置文件的加载及BeanFactory的创建_第14张图片
图17. BeanDefinitionParserDelegate中与属性对应的常量

属性具体的用法请读者翻阅Spring in Action或者其他资料,这里只分析流程不做详细解释,回到图16。 preProcessor(Element)postProcessXml(Element)分别用户子类继承重写对xml解析进行前置处理、后置处理。 parseBeanDefinitions(Element, BeanDefinitionParserDelegate)对我们常用的,诸如 进行解析

后记
为了突出主要流程,在文中留了很多可继续深挖的扩展点,这些扩展点后期会用“外传”的形式另起文章分析。随着源码阅读的深入,对之前文章中知识点的理解必定也会有所不同,所以之后会随时对Spring相关文章进行修改更新

你可能感兴趣的:(Spring解析之IoC:XML配置文件的加载及BeanFactory的创建)