本篇笔记主要记录了以下内容:
使用 ClassPathXmlApplicationContext,通过在 xml 注册一个
bean,跟踪代码,了解它从配置文件的 <bean> 标签,加载到 BeanFactory
注册表 beanDefinitionMap 的详细过程。
layout: post- ClassPathXmlApplicationContext - 设置配置文件路径 - Profile - PropertySource 接口
展示的代码摘取了一些核心方法,去掉一些默认设置和日志输出,还有大多数错误异常也去掉了,小伙伴想看详细代码,注释和 demo,可以下载我上传的笔记项目📒
通过阅读源码的过程,了解设计者的设计思路和从中学习,对 spring
有个基础的了解。
ClassPathXmlApplicationContext
ClassPathXmlApplicationContext 的继承体系结构图:

这种结构图是通过 IDEA 编辑器的 Diagrams
功能展示的,对当前类右键选择,可以看到继承体系,继承了哪些类和引用了哪些接口,方便我们去了解~
ClassPathXmlApplicationContext 继承自 AbstractApplicationContext,而
AbstractRefreshableApplicationContext 是 AbstractApplicationContext
的抽象子类,使用的类注册工厂是
DefaultListableBeanFactory,这个注册工厂也很重要,后面会有它的介绍。
简单来说,DefaultListableBeanFactory 是 Spring 注册及加载 bean
的默认实现,它会将注册的 bean放入 beanDefinitionMap 进行 key-value
形式存储。
在图片的右上角能看到,ResourceLoader
是它的顶层接口,表示这个类实现了资源加载功能。
构造器的代码:
1 | |
设置配置文件路径
org.springframework.context.support.AbstractRefreshableConfigApplicationContext
1 | |
resolvePath,用途是:解析给定的路径,用对应的占位符(placeholder)替换占位符
例如
new ClassPathXmlApplicationContext("classpath:config.xml");,就需要解析
classpath,变成正确路径。
1 | |
我们有不同的运行环境,dev,test 或者
prod,这个时候加载的配置文件和属性应该有所不同,这个时候就需要使用到
Environment 来进行区分。
Spring 环境和属性是由四个部分组成:
Environment: 环境,由Profile和PropertyResolver组合。Profile: 配置文件,可以理解为,容器里多个配置组别的属性和bean,只有激活的profile,它对应的组别属性和bean才会被加载PropertySource: 属性源, 使用CopyOnWriteArrayList数组进行属性对key-value形式存储PropertyResolver:属性解析器,这个用途就是解析属性
Profile
通过这个属性,可以同时在配置文件中部署两套配置,用来适用于生产环境和开发环境,这样可以方便的进行切换开发、部署环境,常用来更换不同的数据库或者配置文件。
demo:(引用自参考资料第四条)
1 | |
有两种方式可以设置选择使用哪套配置:
① 在 web.xml 中设置
1 | |
② 在代码启动时设置
1 | |
PropertySource 接口
继承体系如图:

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

到时这些参数就能在启动的应用中,通过上下文 context 进行获取
1 | |
Bean 的解析和注册
Spring bean 的解析和注册有一个重要的方法 refresh()
AbstractApplicationContext.refresh()
1 | |
下面会围绕这个方法进行跟踪和分析。
具体校验的方法
org.springframework.core.env.AbstractPropertyResolver#validateRequiredProperties
1 | |
可以看到,校验逻辑是遍历 requiredProperties,它是一个字符
Set,默认情况下是空,表示不需要校验任何元素,如果列表中有值,然后根据
key 获取对应的环境变量为空,将会抛出异常,导致 Spring
容器初始化失败。
获取 bean 容器
在这行代码中
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
具体调用的是 :
org.springframework.context.support.AbstractRefreshableApplicationContext#refreshBeanFactory
1 | |
这个入口方法很重要,在这一步新建了 bean 容器和解析 bean,并将 bean
注册到容器中。
BanFactory 自定义
具体方法如下,通过这个方法,可以对工厂进行定制化设置,让子类进行自由配置:
org.springframework.context.support.AbstractRefreshableApplicationContext#customizeBeanFactory
1 | |
EntityResolver

接口全路径是:org.xml.sax.EntityResolver,具体解析使用的方法是:
org.springframework.beans.factory.xml.ResourceEntityResolver#resolveEntity
该方法是用于解析 schema 和 dtd,具体深究的话也很复杂,但解析 xml
不是我想了解的点,所以先跳过~
配置文件加载
入口方法:(由于有多个重名方法,所以复制路径时,将参数的类型也拷贝了)
org.springframework.beans.factory.support.AbstractBeanDefinitionReader#loadBeanDefinitions(java.lang.String, java.util.Set<org.springframework.core.io.Resource>)
核心方法是这两行
1 | |
获取资源文件后,开始解析资源文件(也就是一开始传参的
config.xml),将它转换成 Document
跟踪代码可以看到,进行解析的资源文件从 Resource 包装成
EncodeResouce,为输入流添加了字符编码(默认为 null),体现了设计模式
- 装饰器模式
遍历资源文件,进行转换,核心方法是以下两行:
org.springframework.beans.factory.xml.XmlBeanDefinitionReader#loadBeanDefinitions(org.springframework.core.io.support.EncodedResource)
1 | |
默认标签解析
这部分不会细说,之后再写一篇进行补充,所以简单的过下代码中,是如何解析默认标签的
- IMPORT:导入标签
- ALIAS:别名标签
- BEAN:
bean标签 - NESTED_BEANS:
beans标签(嵌套的beans)
org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader#parseDefaultElement
1 | |
让我们来看下如何解析 bean 标签
获取 id 和 name
org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parseBeanDefinitionElement(org.w3c.dom.Element, org.springframework.beans.factory.config.BeanDefinition)
1 | |
获取 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)
1 | |
初始化 BeanDefiniton 在这个方法中:(具体实现是它的子类
GenericBeanDefinition 噢~)
BeanDefinitionReaderUtils.createBeanDefinition(parentName, className, this.readerContext.getBeanClassLoader())
1 | |
后面就是解析其它标签的内容,之后会补坑~
BeanDefinitionHolder 修饰
org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#decorateBeanDefinitionIfRequired(org.w3c.dom.Element, org.springframework.beans.factory.config.BeanDefinitionHolder, org.springframework.beans.factory.config.BeanDefinition)
1 | |
在之前的常规属性解析后,在这一步操作中,主要用来完成自定义标签元素的解析,这里继续留个坑~
prepareBeanFactory
准备类加载器的环境,对前面获取到的
beanFactory(ConfigurationListableBeanFactory) 进行相关的设置,包括
ClassLoader, post-processors等
invokeBeanFactoryPostProcessors
实例化并调用所有注册的
BeanFactoryPostProcessorBean,这些是后处理器,处理类型是
BeanFactory, Spring 容器允许在实例化 bean 前,读取 bean
信息和修改它的属性。
相当于在实例化前,给用户最后一次机会去修改 bean 信息。
还有一点,执行也可以有先后顺序,依据这些处理器是否实现 PriorityOrdered
、Order 接口,根据 order 值进行排序。
initMessageSource
初始化此上下文的消息源
onRefresh
模板方法,可被重写以添加特定于上下文的刷新工作。
在实例化单例之前调用特殊 bean 的初始化。(雾,不知道是啥特殊 bean
,留个坑=-=)
此实现为空。
finishBeanFactoryInitialization
完成 bean 容器的初始化,实例化所有剩余的(非惰性初始化)单例
resetCommonCaches
真真注册的最后一步,用来清除缓存
重置
Spring核心中的公共内省缓存,因为我们可能再也不需要单例bean的元数据了
踩坑记录
Javadoc 编译错误
Javadoc generation failed. Generated Javadoc options file (useful for troubleshooting)
在编译时,发现无法成功,提示 Javadoc 的错误,解决方法是在 gradle
文件中添加以下配置:
1 | |
传送门:
参考资料
1、spring-analysis/note/Spring.md
2、Spring Framework 5.0.0.M3中文文档
3、Spring 源码深度解析 / 郝佳编著. – 北京 : 人民邮电出版社