
Spring 是这样加载 xml 配置的[TOC]
本篇笔记主要记录了以下内容:
使用 ClassPathXmlApplicationContext,通过在 xml 注册一个 bean,跟踪代码,了解它从配置文件的 <bean> 标签,加载到 BeanFactory 注册表 beanDefinitionMap 的详细过程。
展示的代码摘取了一些核心方法,去掉一些默认设置和日志输出,还有大多数错误异常也去掉了,小伙伴想看详细代码,注释和 demo,可以下载我上传的笔记项目📒
通过阅读源码的过程,了解设计者的设计思路和从中学习,对 spring 有个基础的了解。
基础结构
一开始先介绍如何在代码中注册和使用 bean:
config.xml
| 
 | 
 | 
定义一个简单类:
SimpleBook.java
| 
 | 
 | 
使用 ClassPathXmlApplicationContext 从 xml 配置文件中获取 bean:
| 
 | 
 | 
正常运行代码后,控制台会输出:
| 
 | 
 | 
通常来说,我们要使用一个对象,需要通过 new 初始化,分配内存空间等操作进行实例化,但有了 Spring 容器后,我们可以将 SimpleBook 交给了 Spring 进行管理,不需要在代码中进行 new SimpleBook 等操作,通过自动注入(例如 @Autowire 注解),或者像例子中的,获取上下文对象,然后使用 getBean()方法,可以方便的获取对象实例~。
ClassPathXmlApplicationContext
ClassPathXmlApplicationContext 的继承体系结构图:

这种结构图是通过 IDEA 编辑器的 Diagrams 功能展示的,对当前类右键选择,可以看到继承体系,继承了哪些类和引用了哪些接口,方便我们去了解~ 
ClassPathXmlApplicationContext 继承自 AbstractApplicationContext,而 AbstractRefreshableApplicationContext 是 AbstractApplicationContext 的抽象子类,使用的类注册工厂是 DefaultListableBeanFactory,这个注册工厂也很重要,后面会有它的介绍。
简单来说,DefaultListableBeanFactory 是 Spring 注册及加载 bean 的默认实现,它会将注册的 bean放入 beanDefinitionMap 进行 key-value 形式存储。
在图片的右上角能看到,ResourceLoader 是它的顶层接口,表示这个类实现了资源加载功能。
构造器的代码:
| 
 | 
 | 
构造器
从这行代码看出,子类构造器调用了父类的构造器:
super(parent)
一直跟踪代码,发现从子类开始,沿着父类一直往上调用,直到 AbstractApplicationContext :
| 
 | 
 | 
| 
 | 
 | 
初始化函数主要用来设定资源匹配的处理器,ResourcePatternResolver 接口定义了将位置模式(例如, ant样式的路径模式)解析为资源对象的策略,具体实现类是 PathMatchingResourcePatternResolver (路径匹配资源模式解析器,用来解析我们传入的路径 config.xml)
设置配置文件路径
org.springframework.context.support.AbstractRefreshableConfigApplicationContext
| 
 | 
 | 
resolvePath,用途是:解析给定的路径,用对应的占位符(placeholder)替换占位符
例如 new ClassPathXmlApplicationContext("classpath:config.xml");,就需要解析 classpath,变成正确路径。
| 
 | 
 | 
我们有不同的运行环境,dev,test 或者 prod,这个时候加载的配置文件和属性应该有所不同,这个时候就需要使用到 Environment 来进行区分。
Spring 环境和属性是由四个部分组成:
- Environment: 环境,由- Profile和- PropertyResolver组合。
- Profile: 配置文件,可以理解为,容器里多个配置组别的属性和- bean,只有激活的- profile,它对应的组别属性和- bean才会被加载
- PropertySource: 属性源, 使用- CopyOnWriteArrayList数组进行属性对- key-value形式存储
- PropertyResolver:属性解析器,这个用途就是解析属性
Environment
首先来看 StandardServletEnvironment 的继承体系:

可以看到,顶层接口是 PropertyResolver,它是用来解析属性的,最终解析调用方法的是
PropertyPlaceholderHelper.replacePlaceholders
| 
 | 
 | 
Profile
通过这个属性,可以同时在配置文件中部署两套配置,用来适用于生产环境和开发环境,这样可以方便的进行切换开发、部署环境,常用来更换不同的数据库或者配置文件。
demo:(引用自参考资料第四条)
| 
 | 
 | 
有两种方式可以设置选择使用哪套配置:
① 在 web.xml 中设置
| 
 | 
 | 
② 在代码启动时设置
| 
 | 
 | 
Property
Property 官方注释描述:
| 
 | 
 | 
在 AbstractEnvironment.java 中能找到,在设置环境 env 时,new 了一个 MutablePropertySources,用这个对象来保存属性 :
| 
 | 
 | 
PropertySource 接口
继承体系如图:

从 PropertySource 继承体系来看,customizePropertySources 方法的调用链路是从子类一直往上调用 :
AbstractEnvironment -> StandardServletEnvironment -> StandardEnvironment
最终在 StandardEnvironment 使用 CopyOnWriteArrayList 数组进行属性存储
| 
 | 
 | 
例如从上面可以看出,propertySourceList 将会存储系统的参数:

到时这些参数就能在启动的应用中,通过上下文 context 进行获取
| 
 | 
 | 
小结
刚才一系列的前奏工作,只是用来识别路径资源和加载系统参数
- 设定构造器
- 识别路径变量
- 设定环境参数:主要是 Environment体系,还有在propertySources中保存了运行时的参数
Bean 的解析和注册
Spring bean 的解析和注册有一个重要的方法 refresh()
AbstractApplicationContext.refresh()
| 
 | 
 | 
下面会围绕这个方法进行跟踪和分析。
prepareRefresh 准备更新
该方法作用:准备此上下文用于刷新、设置其启动日期和 active 标志,以及执行任何属性源的初始化。
| 
 | 
 | 
具体校验的方法
org.springframework.core.env.AbstractPropertyResolver#validateRequiredProperties
| 
 | 
 | 
可以看到,校验逻辑是遍历 requiredProperties,它是一个字符 Set,默认情况下是空,表示不需要校验任何元素,如果列表中有值,然后根据 key 获取对应的环境变量为空,将会抛出异常,导致 Spring 容器初始化失败。
自定义环境变量校验
既然给出了 requireProperties 列表,表示我们能够往里面自定义添加,需要校验的环境变量:
- 创建一个类,继承自 AnnotationConfigServletWebServerApplicationContext,重载initPropertySources
- 在应用启动时,将自己新建的类设定成应用上下文(application.setApplicationContextClass(CustomContext.class);)
例如:(引用自参考资料第五条)
| 
 | 
 | 
通过添加自定义的校验值,在 Spring 应用启动时,就能提前进行校验
获取 bean 容器
在这行代码中 ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
具体调用的是 :
org.springframework.context.support.AbstractRefreshableApplicationContext#refreshBeanFactory
| 
 | 
 | 
这个入口方法很重要,在这一步新建了 bean 容器和解析 bean,并将 bean 注册到容器中。
BeanFactory 继承体系
本次例子以及多数情况下,使用的 bean 容器都是 DefaultListableBeanFactory,所以来介绍一下它的继承体系:

可以看出,继承体系十分庞大,继承了多个注册器和实现多个接口,常用的是单例 Singleton 注册器和别名 Alias 注册器,这两个概念也很庞大,可以先简单熟悉下,知道容器默认的对象是单例模式,还有可以通过别名来找到 bean,之后有机会再详细介绍吧。
BanFactory 自定义
具体方法如下,通过这个方法,可以对工厂进行定制化设置,让子类进行自由配置:
org.springframework.context.support.AbstractRefreshableApplicationContext#customizeBeanFactory
| 
 | 
 | 
Bean 加载和解析
核心方法是这个:
org.springframework.context.support.AbstractXmlApplicationContext#loadBeanDefinitions(org.springframework.beans.factory.support.DefaultListableBeanFactory)
| 
 | 
 | 
在解析 XML 中,使用到以下两个继承体系:EntityResolver 和 BeanDefinitionReader
EntityResolver

接口全路径是:org.xml.sax.EntityResolver,具体解析使用的方法是:
org.springframework.beans.factory.xml.ResourceEntityResolver#resolveEntity
该方法是用于解析 schema 和 dtd,具体深究的话也很复杂,但解析 xml 不是我想了解的点,所以先跳过~
BeanDefinitionReader

顶级接口是 BeanDefinitionReader,用于 XML Bean 定义的 Bean定义阅读器。将实际读取的 XML 文档委托给实现。
这两个类用途很明了,就是将 XML 转成输入流,感兴趣的同学可以继续深入跟踪~
配置文件加载
入口方法:(由于有多个重名方法,所以复制路径时,将参数的类型也拷贝了)
org.springframework.beans.factory.support.AbstractBeanDefinitionReader#loadBeanDefinitions(java.lang.String, java.util.Set
) 
核心方法是这两行
| 
 | 
 | 
获取资源文件后,开始解析资源文件(也就是一开始传参的 config.xml),将它转换成 Document
跟踪代码可以看到,进行解析的资源文件从 Resource 包装成 EncodeResouce,为输入流添加了字符编码(默认为 null),体现了设计模式 - 装饰器模式
遍历资源文件,进行转换,核心方法是以下两行:
org.springframework.beans.factory.xml.XmlBeanDefinitionReader#loadBeanDefinitions(org.springframework.core.io.support.EncodedResource)
| 
 | 
 | 
Bean 解析
org.springframework.beans.factory.xml.XmlBeanDefinitionReader#doLoadBeanDefinitions
| 
 | 
 | 
在 doLoadDocument() 方法中,将资源文件解析成 docuemnt 文档
org.springframework.beans.factory.xml.XmlBeanDefinitionReader#registerBeanDefinitions
| 
 | 
 | 
这里不多介绍如何转换成 document 和 documentReader 初始化,感兴趣的同学请继续跟踪~
下面要说的是 bean 容器 DefaultListableBeanFactory 解析 document
org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader#doRegisterBeanDefinitions
| 
 | 
 | 
从上面可以看出,在解析之前,如果命名空间是以 http://www.springframework.org/schema/beans 开头,将会检查 profile 属性
校验通过后,开始正式解析 doc 元素
org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader#parseBeanDefinitions
在这一步中,我们在 xml 中配置的属性就能对应到 document 对象中,在之后流程中取出使用
默认标签解析
这部分不会细说,之后再写一篇进行补充,所以简单的过下代码中,是如何解析默认标签的
- IMPORT:导入标签
- ALIAS:别名标签
- BEAN:bean标签
- NESTED_BEANS:beans标签(嵌套的beans)
org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader#parseDefaultElement
| 
 | 
 | 
让我们来看下如何解析 bean 标签
bean 标签解析
org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader#processBeanDefinition
| 
 | 
 | 
下面讲下几个关键方法所做的事情
获取 id 和 name
org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parseBeanDefinitionElement(org.w3c.dom.Element, org.springframework.beans.factory.config.BeanDefinition)
| 
 | 
 | 
获取 id 和 name 属性的流程,按照代码注释一步一步往下走就清晰了
该方法主要工作流程如下:
- 提取元素中的 idname属性
- 进一步解析其它所有属性并统一封装到 GenericBeanDefinition类型的实例中
- 检测到 bean没有指定beanName使用默认规则生成beanName
- 将获取到的信息封装到 BeanDefinitionHolder的实例中
对标签中其它属性的解析
org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parseBeanDefinitionElement(org.w3c.dom.Element, java.lang.String, org.springframework.beans.factory.config.BeanDefinition)
| 
 | 
 | 
初始化 BeanDefiniton 在这个方法中:(具体实现是它的子类 GenericBeanDefinition 噢~)
BeanDefinitionReaderUtils.createBeanDefinition(parentName, className, this.readerContext.getBeanClassLoader())
| 
 | 
 | 
后面就是解析其它标签的内容,之后会补坑~
BeanDefinition 继承体系

从图中可以看出,BeanDefinition 是一个接口,GenericBeanDefinition 、RootBeanDefinition、ChildBeanDefinition,这三者都继承了 AbstractBeanDefinition。
其中 BeanDefinition 是配置文件 <bean> 元素标签在容器中的内部表示形式。
<bean> 元素标签拥有 class、 scope、 lazy-init 等配置属性,BeanDefinition 则提供了相应的 beanClass、 scope、lazyInit 属性,两者是互相对应的。
BeanDefinitionHolder 修饰
org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#decorateBeanDefinitionIfRequired(org.w3c.dom.Element, org.springframework.beans.factory.config.BeanDefinitionHolder, org.springframework.beans.factory.config.BeanDefinition)
| 
 | 
 | 
在之前的常规属性解析后,在这一步操作中,主要用来完成自定义标签元素的解析,这里继续留个坑~
Bean 注册
经历千辛万苦,通过上面一些列的解析操作,终于到了注册 bean 信息的方法
org.springframework.beans.factory.support.BeanDefinitionReaderUtils#registerBeanDefinition
| 
 | 
 | 
上面也说过,这里使用的 bean 容器是 DefaultListableBeanFactory,注册方法关键操作时以下两行代码:
org.springframework.beans.factory.support.DefaultListableBeanFactory#registerBeanDefinition
| 
 | 
 | 
到了这一步,将 bean 信息放入到 beanDefinitionMap,完成了类注册的操作~
为了描述代码逻辑的完整性,对以下一些方法进行简单介绍。
prepareBeanFactory
准备类加载器的环境,对前面获取到的 beanFactory(ConfigurationListableBeanFactory) 进行相关的设置,包括 ClassLoader, post-processors等
postProcessBeanFactory
将加载所有 bean 定义,但还没有实例化 bean 时,在应用程序上下文的标准初始化之后修改它的内部 bean 容器。
这允许在特定的 ApplicationContext 实现中注册特殊的 beanpostprocessor 等。
这也是一个空方法,等子类去实现
invokeBeanFactoryPostProcessors
实例化并调用所有注册的 BeanFactoryPostProcessorBean,这些是后处理器,处理类型是 BeanFactory, Spring 容器允许在实例化 bean 前,读取 bean 信息和修改它的属性。
相当于在实例化前,给用户最后一次机会去修改 bean 信息。
还有一点,执行也可以有先后顺序,依据这些处理器是否实现 PriorityOrdered 、Order 接口,根据 order 值进行排序。
registerBeanPostProcessors
实例化并注册所有后处理器,跟上面的不一样,这个方法处理的类型是 Bean ,跟上面方法一样的是,也有优先级的概念~
initMessageSource
初始化此上下文的消息源
initApplicationEventMulticaster
初始化此上下文的事件多播程序
onRefresh
模板方法,可被重写以添加特定于上下文的刷新工作。
在实例化单例之前调用特殊 bean 的初始化。(雾,不知道是啥特殊 bean ,留个坑=-=)
此实现为空。
registerListeners
检查侦听器 bean 并注册它们
事件监听者类型是 java.util.EventListener
finishBeanFactoryInitialization
完成 bean 容器的初始化,实例化所有剩余的(非惰性初始化)单例
finishRefresh
最后一步,发布相应的事件
事件的类型是:java.util.EventObject
resetCommonCaches
真真注册的最后一步,用来清除缓存
重置
Spring核心中的公共内省缓存,因为我们可能再也不需要单例bean的元数据了
总结
本章笔记只是记录了一个 bean 如何从 xml 加载到 bean 容器的注册表中,经历了多行代码,终于摸清调用链路。
这里总结一下核心的 loadBeanDefinitions(beanFactory) 工作流程:
① 读取配置文件
- 封装资源文件:获取路径文件,封装成 EncodeResource
- 获取输入流:从 Resource中获取对应的InputStream并构造InputSource
- 传递参数:通过构造的 InputSource实例和Resource实例,传递给doLoadBeanDefinitions方法
② 加载 bean
- 获取对 XML资源文件的验证模式
- 加载 XML资源文件,解析成对应的Document文档:里面有多个Node节点信息,保存了我们写的配置信息
- 根据 Document文件进行Bean信息解析
③ bean 标签的解析和注册
- 委托 BeanDefinitionDelegate类的parseBeanDefinitionElement方法:对元素进行解析,返回BeanDefinitionHolder类型的实例(里面包含了class、name、id、alias等属性)
- 解析标签:判断标签类型,看解析的是默认标签还是自定义标签
- 对 bdHodler进行注册:解析完成后,注册bean信息,注册操作委托给了BeanDefinitionReaderUtils的registerBeanDefinition方法
- 发送响应事件:通知相关的监听器,通知 bean容器已经加载完成
下一篇笔记再会~
踩坑记录
Javadoc generation failed. Generated Javadoc options file (useful for troubleshooting)
在编译时,发现无法成功,提示 Javadoc 的错误,解决方法是在 gradle 文件中添加以下配置: 
| 
 | 
 | 
传送门:
参考资料
1、spring-analysis/note/Spring.md
2、Spring Framework 5.0.0.M3中文文档
3、Spring 源码深度解析 / 郝佳编著. – 北京 : 人民邮电出版社