Spring 源码解析 - Bean资源加载注册过程
创始人
2025-05-28 13:39:51
0

一、Spring Bean资源加载注册过程

在使用 Spring 时,一般有两种方式,一种是使用 Xml 的形式定义 Bean 信息,另一种是使用注解的方式,本篇文章带领大家一起解析下当使用 Xml 的方式下,Spring 是如何加载资源并进行注册的。

在开始源码解读前,先来回顾下 Xml 方式的使用:

首先创建一个测试 Bean

public class TestSpring {public void test() {System.out.println("test spring !");}
}

然后声明一个 applicationContext.xml 资源配置文件:



最后使用 ClassPathXmlApplicationContext 加载资源,并使用:

public class Test {public static void main(String[] args) {ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");TestSpring testSpring = context.getBean(TestSpring.class);testSpring.test();}
}

在这里插入图片描述

简单的例子过后,下面我们就要思考,Spring 是如何加载的 Xml 资源,又是如果解析bean 信息进行注册。

带着这个疑问,一起去阅读下源码。

二、Spring 资源加载过程

上面例子我们使用的 ClassPathXmlApplicationContext ,那下面就从它的构造方法入口解析,首先点到 ClassPathXmlApplicationContext 的构造方法中,又调用了当前类三个参数的构造方法,注意这里 refresh 给的 trueparent 给的 null

在这里插入图片描述
在这里插入图片描述

在构造方法中,首先对父类的构造方法进行初始化,这里一直跟踪过去其实是初始化了 AbstractApplicationContext 类的构造方法,在该方法中,主要指定了 Spring Resource 的加载器

在这里插入图片描述

getResourcePatternResolver 方法中,可以看到默认创建了一个 PathMatchingResourcePatternResolver 并将当前类传入,因为这里的 AbstractApplicationContext 继承自 DefaultResourceLoader ,因此也是一个资源加载器,其中加载器的 getResource(String location) 方法就用于载入资源:

在这里插入图片描述

在回到 ClassPathXmlApplicationContext 的构造方法中,紧接着使用 setConfigLocations方法,将字符串的资源文件名称解析为路径,并放在 AbstractRefreshableConfigApplicationContext 类的 configLocations 数组中:

在这里插入图片描述

下面在回到 ClassPathXmlApplicationContext 的构造方法中,由于前面构造方法中 refresh 传的 true ,所以这里会触发 refresh() 方法,该方法实际触发的是 AbstractApplicationContext 中的 refresh() 方法,、 Spring IOC 的大部分核心功能都在 refresh() 方法中进行触发,下面进入到该方法中:

在这里插入图片描述

首先对 startupShutdownMonitor 进行上锁,查看 startupShutdownMonitor 的使用情况,可以发现在容器关闭时也对其进行了上锁,主要就是为了防止放生冲突,在锁中可以看到又触发了很多方法,逻辑比较多,这里主要看 obtainFreshBeanFactory() 方法,在这里面定义了 BeanFactory 工厂的创建,以及 IOC容器的注册:

在这里插入图片描述

在这里插入图片描述

进入到 obtainFreshBeanFactory() 方法中,首先触发了 refreshBeanFactory() 方法,其实是调用了 AbstractRefreshableApplicationContextrefreshBeanFactory()方法,进入到该方法内:

在这里插入图片描述
可以看到,在该方法内首先如果存在 BeanFactory 的话,就将其中的 Bean 销毁,并关闭工厂。
接着再创建一个新的 BeanFactory ,默认就是使用的 DefaultListableBeanFactory ,下面的 customizeBeanFactory 方法是对 BeanFactory 进行设置,如设置启动参数,开启注解的自动装配等,主要的逻辑在 loadBeanDefinitions 中,由于使用的是 ClassPathXmlApplicationContext 这里实际为子类 AbstractXmlApplicationContextloadBeanDefinitions 方法:

在这里插入图片描述

在该方法中,首先创建了一个 XmlBean 读取器,并为其设置环境信息,以及 Xml 解析器等,下面在 initBeanDefinitionReader 方法中启用了 Xml 的校验机制,主要来看 loadBeanDefinitions 方法:

在这里插入图片描述
loadBeanDefinitions 方法中,由于前面使用的 ClassPathXmlApplicationContextClassPathXmlApplicationContext(String configLocation) 构造方法,因此这个会进入到第二个判断中,触发
AbstractBeanDefinitionReader 中的 loadBeanDefinitions 方法:

在这里插入图片描述

然后又触发了当前类的 loadBeanDefinitions(String location) 方法,接着又进入 loadBeanDefinitions(String location, Set actualResources) 方法,注意这里 actualResources 参数给的 null

在这里插入图片描述
在这里插入图片描述

loadBeanDefinitions(String location, Set actualResources) 方法中,首先判断资源加载器是否为 ResourcePatternResolver , 这个我们前面在ClassPathXmlApplicationContext构造函数的调用链中发现是使用的 PathMatchingResourcePatternResolver ,而 PathMatchingResourcePatternResolver 又 实现了 ResourcePatternResolver,因此这里的判断可以满足:

在这里插入图片描述

loadBeanDefinitions(String location, Set actualResources) 方法的判断中,首先将资源地址转为了 Resource 形式,接着使用了 loadBeanDefinitions(Resource... resources) 方法进行资源的加载:

在这里插入图片描述
loadBeanDefinitions(Resource... resources) 方法中,又触发了加载单个资源的 loadBeanDefinitions(Resource resource) 方法,而该方法由子类实现,这里主要进到了 XmlBeanDefinitionReaderloadBeanDefinitions(Resource resource) 方法中:

在这里插入图片描述

接着在来到 loadBeanDefinitions(EncodedResource encodedResource) 方法中,在该方法中,将读取资源文件为输入流,然后使用 doLoadBeanDefinitions 进行解析:

在这里插入图片描述

来到 doLoadBeanDefinitions 方法中:

在这里插入图片描述

在该方法中做了两个重要的操作,首先将 Xml 文件解析为了 Document 对象,然后再对 Document 对象进行解析注册 BeanDefinition ,这里先看一个 doLoadDocument 是如何解析 Xml 文件的:

在这里插入图片描述

其中 getValidationModeForResource 方法,主要校验 Xml 格式是否正确,而 documentLoader 则默认使用了 DefaultDocumentLoader 进行加载解析 Xml 文件,下面可以进到 DefaultDocumentLoaderloadDocument 方法中:

在这里插入图片描述

在这个方法中,首先创建了一个文件解析工厂,使用的是 JAXPDocumentBuilderFactory,然后又创建了一个文件解析器,使用的 JAXPDocumentBuilder ,最后将Xml 资源解析为 JAXPDocument 对象返回给上层。

下面再回到 doLoadBeanDefinitions 方法中,解析出来的 Document 对象,给到了 registerBeanDefinitions(Document doc, Resource resource) 方法中,进到该方法下:

在这里插入图片描述
在该方法中,首先创建了一个 Document 类的解析器,并使用 getRegistry().getBeanDefinitionCount() 方法获取了已注册的数量,这里可以到点到 getBeanDefinitionCount 中,其实是 AbstractApplicationContext 类下的 getBeanDefinitionCount 方法,一直向上找,可以找到 beanDefinitionMap 集合:

在这里插入图片描述

其实这个就是 IOC 容器,存储 BeanDefinition 对象,其实就是 bean 的定义对象。

再回到 registerBeanDefinitions(Document doc, Resource resource) 方法中,主要来分析 documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); 方法,该方法则就是做了获取 Document 中的 Bean 信息,并注册到了 beanDefinitionMap 中,进入到该方法中:

在这里插入图片描述

在这里首先获取了 Xml 描述符,然后拿到了根节点数据,接着进入到 doRegisterBeanDefinitions 方法下:

在这里插入图片描述

doRegisterBeanDefinitions 方法下,主要关注 parseBeanDefinitions 方法的逻辑,其中 preProcessXmlpostProcessXml 则是用来子类进行扩展的,进到 parseBeanDefinitions 方法下:

在这里插入图片描述

这里会判断 Xml 中的命名空间是否是默认的还是自定义的,如果是自定义的则使用用户自定义的解析规则,否则使用默认的解析,由于前面例子中的 Xml 文件就是使用的默认的,因此这里会使用 parseDefaultElement 方法进行解析,进到该方法下:

在这里插入图片描述

到这里就比较容器理解了,分别对 Xml 中的 import、alias、bean、beans 节点进行解析,这里逻辑比较多,我们主要关注下对 bean 的解析,进到 processBeanDefinition 方法中:

在这里插入图片描述
在该方法下,首先将 Element 节点,解析成 BeanDefinitionHolder 对象,然后进行容器的注册,并在最后注册完成发送事件,这里先看下 delegate.parseBeanDefinitionElement(ele) 方法是如何生成 BeanDefinitionHolder 对象:

在这里插入图片描述

这里又触发了当前类的 parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) 方法,并 containingBean 默认给的 null ,进到该方法下:

在这里插入图片描述
在该方法中首先获取 bean 下的 idname 元素,其中 name 就是别名,因此这里对别名进行了单独记录,而 beanName 则默认使用 id ,如果没有配置 id ,则使用第一个别名,如果别名也没有配置的话,下面的逻辑中会根据 Class 名称自动生成一个,下面接着向下看

在这里插入图片描述

上面提到 containingBean 默认为null,因此下面会检测 bean 元素的 idname 的唯一性,接着会触发 parseBeanDefinitionElement 在该方法中则做了更加详细的元素解析,并放在 AbstractBeanDefinition 中进行存储,进入到该方法下:

在这里插入图片描述
在这里插入图片描述

从上面的注释就不难猜出大体逻辑了,分别对 Xml 中的 class、parent、meta 等标签的解析,并将解析内容存放至 AbstractBeanDefinition 对象中,这里可以看下该对象存的内容:

在这里插入图片描述

由于上面案例中的 Xml 没有做过多的配置,因此这里都是 null ,但从这些属性上不难看出,对应着 Xml 中不同标签的存储。

下面再回到 parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) 方法中,下面的逻辑就会判断 如果解析后没有 idname 别名,则生成一个唯一的 bean 名称:

在这里插入图片描述

最后将解析后的信息,组装成一个 BeanDefinitionHolder 对象返回给上层:

在这里插入图片描述

到这我们就可以回到前面的 processBeanDefinition 方法中了

在这里插入图片描述

该方法下,接着会使用 BeanDefinitionReaderUtils.registerBeanDefinition 方法,将 Bean 定义信息注册到 IOC 容器中,进到该方法下看是如何注册的:

在这里插入图片描述
可以看到,分了两个步骤,首先使用 beanName 注册 BeanDefinition,然后又注册了别名和beanName之间的关系,这里先看下 registry.registerBeanDefinition 方法注册 BeanDefinition,由于前面创建工厂时创建的是 DefaultListableBeanFactory ,因此该方法就是触发的该工厂下的 registerBeanDefinition,进到该方法下:

在这里插入图片描述

在这里首先校验 beanDefinition 是否合法:

在这里插入图片描述

然后会根据 beanName 去容器中获取,如果容器中存在,并不允许覆盖,则抛出异常,还会判断容器中的 BeanDefinition 和当前的角色关系,最后会将新的 BeanDefinition 覆盖容器中旧的:

在这里插入图片描述
下面如果容器中不存在的话,这里会判断容器是否已经初始化好,使用的是工厂中的 alreadyCreated 容器,该容器在后面使用 Bean 对象时会对已经初始化好的对象进行记录,因此这里可以用来判断容器是否初始化完成,如果容器已经初始化,则为了避免数据的不统一,这里对 beanDefinitionMap 容器进行加锁:

在这里插入图片描述
如果成功获取到锁,则将当前的 beanDefinition 加入到容器中,并对该 beanName 进行记录。

在这里插入图片描述
下面这里如果容器还没初始化好,则直接添加即可,最后会检查,被覆盖的BeanDefinition 是否有同名的已经注册到单例缓存容器,如果有的话则需要重置注册的 BeanDefinition,就是清空当前的容器,重新进行注册。

到这其实就已经完成了 Bean 资源的加载注册过程,不过上面分析中 registerBeanDefinition方法还没有走完,下面还会进行别名的注册:

在这里插入图片描述

这里也一起看了,这里 DefaultListableBeanFactory 并没有提供 registerAlias 方法,其实这个方式是触发的 SimpleAliasRegistry 下的 registerAlias 方法,进到该方法下:

在这里插入图片描述

可以看到,对于别名则是使用的 aliasMap 容器进行存储,而 aliasMap 就是一个ConcurrentHashMap,key 为别名,value 为注册容器中 bean 的名称。

在这里插入图片描述

到这里就已经分析完了Bean资源的加载和注册过程。

相关内容

热门资讯

喜欢穿一身黑的男生性格(喜欢穿... 今天百科达人给各位分享喜欢穿一身黑的男生性格的知识,其中也会对喜欢穿一身黑衣服的男人人好相处吗进行解...
网络用语zl是什么意思(zl是... 今天给各位分享网络用语zl是什么意思的知识,其中也会对zl是啥意思是什么网络用语进行解释,如果能碰巧...
发春是什么意思(思春和发春是什... 本篇文章极速百科给大家谈谈发春是什么意思,以及思春和发春是什么意思对应的知识点,希望对各位有所帮助,...
苏州离哪个飞机场近(苏州离哪个... 本篇文章极速百科小编给大家谈谈苏州离哪个飞机场近,以及苏州离哪个飞机场近点对应的知识点,希望对各位有...
为什么酷狗音乐自己唱的歌不能下... 本篇文章极速百科小编给大家谈谈为什么酷狗音乐自己唱的歌不能下载到本地?,以及为什么酷狗下载的歌曲不是...
家里可以做假山养金鱼吗(假山能... 今天百科达人给各位分享家里可以做假山养金鱼吗的知识,其中也会对假山能放鱼缸里吗进行解释,如果能碰巧解...
华为下载未安装的文件去哪找(华... 今天百科达人给各位分享华为下载未安装的文件去哪找的知识,其中也会对华为下载未安装的文件去哪找到进行解...
四分五裂是什么生肖什么动物(四... 本篇文章极速百科小编给大家谈谈四分五裂是什么生肖什么动物,以及四分五裂打一生肖是什么对应的知识点,希...
怎么往应用助手里添加应用(应用... 今天百科达人给各位分享怎么往应用助手里添加应用的知识,其中也会对应用助手怎么添加微信进行解释,如果能...
客厅放八骏马摆件可以吗(家里摆... 今天给各位分享客厅放八骏马摆件可以吗的知识,其中也会对家里摆八骏马摆件好吗进行解释,如果能碰巧解决你...