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
资源的加载和注册过程。