Spring 源码解析 - Bean资源加载注册过程

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

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

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

首先创建一个测试 Bean :

public class TestSpring {

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

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

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="testSpring" class="com.example.demo.spring.TestSpring"/>

</beans>

最后使用 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 给的 true ,parent 给的 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() 方法,其实是调用了 AbstractRefreshableApplicationContext 的 refreshBeanFactory()方法,进入到该方法内:

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

在这里插入图片描述

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

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

在这里插入图片描述

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

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

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

在这里插入图片描述

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

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

在这里插入图片描述

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

在这里插入图片描述

来到 doLoadBeanDefinitions 方法中:

在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述

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

下面再回到 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 方法的逻辑,其中 preProcessXml 和 postProcessXml 则是用来子类进行扩展的,进到 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 下的 id 和 name 元素,其中 name 就是别名,因此这里对别名进行了单独记录,而 beanName 则默认使用 id ,如果没有配置 id ,则使用第一个别名,如果别名也没有配置的话,下面的逻辑中会根据 Class 名称自动生成一个,下面接着向下看

在这里插入图片描述

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

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

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

在这里插入图片描述

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

下面再回到 parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) 方法中,下面的逻辑就会判断 如果解析后没有 id 和 name 别名,则生成一个唯一的 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资源的加载和注册过程。

posted @ 2024-12-12 09:26  CharyGao  阅读(17)  评论(0编辑  收藏  举报