携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第2天,点击查看活动详情
现如今大部分项目都已经采用了Spring Boot作为项目的基地,对于Spring xml的配置文件用的不在那么频繁,但是对于学习Spring源码来说还是比较重要的,本文将介绍Spring在启动时是如何将xml配置文件进行解析。
一、准备测试类及xml文件
测试案例仍以加载一个bean作为例子
text.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"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean class="com.spring.Bean" id="bean">
<property name="name" value="张三"></property>
<property name="age" value="12"></property>
</bean>
</beans>
Text.class
package com.spring;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Text {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("text.xml");
Bean bean = context.getBean(Bean.class);
System.out.println(bean.getName());
System.out.println(bean.getAge());
}
}
Bean.class
package com.spring;
public class Bean {
private String name;
private Integer age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
二、解析流程
前面了解Spring中的核心方法后,直接把断电打在ObtainFreshBeanFactory上,进入该方法,能够看到有两个方法
这两个方法一个是刷新BeanFactory,一个是获取BeanFactory
跟随断点能够进入到refreshBeanFactory()
这个方法中做了这么几件事:
1、判断是否存在BeanFactory,如果存在进行销毁
2、创建一个新的类型为DefaultListableBeanFactory的bean工厂
3、设置beanFactory的一些属性
4、执行loadBeanDefinition,加载beanDefinition
在这个方法中最重要也是最值得关注的方法就是loadBeanDefinition,后续会有很个名字相同的重载方法,到执行loadBeanDefinition之前 ,我们可以看到此时的bean工厂已经创建完成,接下来就是要进行beanDefinition的填充,可以看一下当前bean工厂的一个状态
进入loadBeanDefinition xml文件的解析可以说正式的开始了
进入AbstractXmlApplicationContext#loadBeanDefinition(DefaultListableBeanFactory beanFactory)方法中,主要做了这么几件事
1、创建一个Xml的阅读器 XmlBeanDefinitionReader
2、给beanDefinitionReader设置一些属性
3、初始化BeanDefinitionReader
4、继续执行重载方法loadBeanDefinition
接着进入重载方法AbstractXmlApplicationContext#loadBeanDefinitions(XmlBeanDefinitionReader reader)
可以看到这个方法中有两个获取配置的地方 ,getConfigResources这个方法我们一般用不到,所以一定为空,主要看的是下面的getConfigLocations,该方法用来获取我们的配置文件名,即ClassPathXmlApplicationContext中传入的text.xml
可以进去getConfigLocations中看一下是怎么获取到的
这里的configLocations就是一个数组,这里是直接进行获取,想象一下这个值是什么时候设置进来的。
向前找我们可以找到在ClassPathXmlApplicationContext中找到一个setConfigLocations(configLocations)方法,这里的入参就是我们上面ClassPathXmlApplicationContext中出入的参数,由于可能是多个配置文件,所以在这个方法中会解析为一个数组存储,当Spring需要加载的时候直接取值用就可以了。
继续解析xml,拿到configLocations后,调用AbstractBeanDefinitionReader#loadBeanDefinitions(String... locations)
这个方法没什么特殊的,就是将配置文件进行循环解析,直接看后面的解析方法
最终会走到
XmlBeanDefinitionReader#loadBeanDefinitions(EncodedResource encodedResource)
该方法中需要注意的点在于
1、将resource转换为了一个inputStream的输入流
2、设置了输入流的字符格式
接着调用doLoadBeanDefinitions,在Spring中有个奇怪的现象,就是以do开头的方法大部分都是真正做事的方法。
XmlBeanDefinitionReader#doLoadBeanDefinitions(InputSource inputSource, Resource resource)
该方法做了:
1、将inputStream输入流转换成一个Document,在这个过程中会去选择dtd还是xsd
2、调用registerBeanDefinitions方法
XmlBeanDefinitionReader#registerBeanDefinitions(Document doc, Resource resource)
1、创建了一个documentReader的读取
2、统计当前注册前的注册数量
3、调用registerBeanDefinitions进行注册
直接跳过中间的过程到
DefaultBeanDefinitionDocumentReader#parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate)
开始正式的解析xml中bean的过程
可以看到方法中比较关键的方法
1、parseDefaultElement(ele, delegate); 用来解析spring默认的标签类
2、delegate.parseCustomElement(ele); 用来解析spring扩展的标签类,例如aop的标签
先看默认标签的解析
DefaultBeanDefinitionDocumentReader#parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate)
可以看到一个有四种类型,分别对应着、、、标签,结合本次测试类的标签,将会processBeanDefinition(ele, delegate); 其余方法不在赘述。
DefaultBeanDefinitionDocumentReader#processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate)
这个方法中会获取到一个BeanDefinitionHolder,然后会把这个holder注册到beanFactory中去,先跳过这个方法,看parseBeanDefinitionElement方法是怎么获得holder
DefaultBeanDefinitionDocumentReader#parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean)
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
//获取到标签上的id值
String id = ele.getAttribute(ID_ATTRIBUTE);
//获取到标签上name的值
String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
//创建一个别名的list
List<String> aliases = new ArrayList<>();
if (StringUtils.hasLength(nameAttr)) {
String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
aliases.addAll(Arrays.asList(nameArr));
}
//beanName 等于id
String beanName = id;
//如果别名不为空的话设置beanName等于list中的第一个元素的值,病删除别名中的第一个元素
if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
beanName = aliases.remove(0);
if (logger.isTraceEnabled()) {
logger.trace("No XML 'id' specified - using '" + beanName +
"' as bean name and " + aliases + " as aliases");
}
}
//containingBean从processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate)方法中进来的为null
if (containingBean == null) {
//检查beanName和别名是否唯一
checkNameUniqueness(beanName, aliases, ele);
}
//将bean中剩下的属性解析出来,并返回一个AbstractBeanDefinition对象回来
AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
if (beanDefinition != null) {
if (!StringUtils.hasText(beanName)) {
try {
if (containingBean != null) {
beanName = BeanDefinitionReaderUtils.generateBeanName(
beanDefinition, this.readerContext.getRegistry(), true);
}
else {
beanName = this.readerContext.generateBeanName(beanDefinition);
// Register an alias for the plain bean class name, if still possible,
// if the generator returned the class name plus a suffix.
// This is expected for Spring 1.2/2.0 backwards compatibility.
//如果生成器返回类名加上后缀,则为普通 bean 类名注册一个别名(如果仍然可能
String beanClassName = beanDefinition.getBeanClassName();
if (beanClassName != null &&
beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&
!this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
aliases.add(beanClassName);
}
}
if (logger.isTraceEnabled()) {
logger.trace("Neither XML 'id' nor 'name' specified - " +
"using generated bean name [" + beanName + "]");
}
}
catch (Exception ex) {
error(ex.getMessage(), ele);
return null;
}
}
String[] aliasesArray = StringUtils.toStringArray(aliases);
return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
}
return null;
}
现在在回过头来看DefaultBeanDefinitionDocumentReader#processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) ,返回了BeanDefinitionHolder已经,到目前为止 xml中的内容都已经被封装进BeanDefinitionHolder中。
beanDefinition的注册
调用BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());完成beanDefinition的注册
三、总结
用一张图来展示来展示整个流程