前言
在Spring加载Bean的过程中是无法感知每个Bean的,也无法知道每一个bean具体的特征,比如某个bean是单例还是原型,是否懒加载等等。所以Spring需要beandefinition来对每个不同的bean进行描述,并不是直接把Bean直接存入容器而是读取成beanDefinition,再根据BeanDefenition对bean的描述进行实例化。那么Spring是如何读取这些BeanDefinition的呢?
准备工作
我们从源码出发一步步Debug来看BeanDefinition读取的过程,再次之前我已经下载编译了Spring的源码,还需要创建xml文件和对于的bean对象。
- 创建类Bean
public class Bean{
private String username = "LYX";
}
- 创建一个spring.xml并注册类Bean
<?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 https://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
<bean class="com.lyx.learn.entity.Teacher" name="teacher">
<property name="name" value="lyx"></property>
</bean>
<bean id="bean" class="com.lyx.learn.entity.Bean">
</bean>
</beans>
读取Xml文件
就以我们常见的ClassPathXmlApplicationContext(“文件路径”)来进行debug,在new ClassPathXmlApplicationContext对象时就将xml文件路径作为参数传入。其内部调用了另一个构造方法,如下图所示
在这个构造方法中会通过setConfigLocations设置资源路径,但只是标记路径并没有进行读取操作。
设置完路径之后就会调用refresh方法,这个方法基本上包含了spring初始化过程的整个周期,具体可以了解我的另一篇文章链接: Spring源码:refresh方法解析。在做完准备工作之后就开始设置容器以及读取beanDefinition了,在obtainFreshBeanFactory方法中我们可以看到具体的读取过程,所以本篇文章只关注obtainFreshBeanFactory方法。
在obtainFreshBeanFactory只关注两件事,就是创建beanFactory容器和读取beanDefinition。这些都是在refreshBeanFactory方法中实现的。它会先判断是否已经存在BeanFactory,如果已经存在就销毁,然后重新创建。
可以关注一下createBeanFactory方法,这个方法就是创建容器的方法。在里面会直接new一个DefaultListableBeanFactory,所以我们常说的容器和beanFactory实际上就是DefaultListableBeanFactory,这里不过多阐述,有兴趣的可以自行去了解一下这个容器。我们重点要说的是loadBeanDefinitions。
在loadBeanDefinitions中会创建一个XmlBeanDefinitionReader对象然后调用XmlBeanDefinitionReader的loadBeanDefinitions方法将开始传入的资源路径开始读取。(除了XmlBeanDefinitionReader还其他BeanDefinitionReader,目的时通过不同方式读取BeanDefinition,它们都实现自接口BeanDefinitionReader)
精华都在这个loadBeanDefinitions中了,在启动spring发现日志打印Loading XML bean definitions from XXX就可以知道初始化进行到这一步了。
我们都知道在Spring中干活的往往都是do开头的(doGetBean,doCreateBean)方法,所以我们得关注doLoadBeanDefinitions方法,入参是xml文件的输入流对象。在doLoadBeanDefinitions中就做了两件事,解析xml文件,把一个个的bean标签解析成document对象后封装成beanDefinition。
doLoadDocument是由实现类DefaultDocumentLoader执行的,其内部会创建一个DocumentBuilder对象,再通过这个builder来解析。解析过程就不说了,就是像剥洋葱一样一层层解析各个子标签。
解析完成后拿到顶层的Document对象,调用registerBeanDefinitions方法,在方法中会交给BeanDefinitionDocumentReader的registerBeanDefinitions来执行。
BeanDefinitionDocumentReader拿到Document对象后直接拿根目录交给doRegisterBeanDefinitions,也像剥洋葱一样层层解析各个标签。
我们先看spring对doRegisterBeanDefinitions的说明,在遇到beans标签是会递归执行,在这里使用了委派模式。但不是我们关注的重点。
解析过程是在parseBeanDefinitions中执行的,preProcessXml和postProcessXml是解析是的前后置方法,默认实现是空的,可以理解为留给开发人员自定义拓展的。
在parseBeanDefinitions中将当前element元素交给parseDefaultElement方法进行解析。
parseDefaultElement
在parseDefaultElement中我们可以看到常用的bean,import,alias等标签,不同的标签交给不同的处理器。我们就关注processBeanDefinition标签看看是如何解析bean标签的。
由下图我们看到spring在拿到bean标签后会通过
委派者的parseBeanDefinitionElement创建一个BeanDefinitionHolder对象,这个BeanDefinitionHolder实际上以内部变量的形式持有了BeanDefinition对象。
在parseBeanDefinitionElement会根据标签的beanName,class等属性创建一个beanDefinition对象,但此时只是封装了class等属性,beanName和beanDefinition对象在这个holder中是作为两个属性存在的。解析过程就不细说了,大致就是拿到各标签的属性值设置到beanDefinition中。直接看返回结果可以发现解析完成后的结果如下图所示
beanDefinitionHolder结构如下图所示
拿到这个beanDefinitionHolder后就准备把beandefinition注册到beanDefinitionMap了(存放beanDefinitions的容器),注册的过程会在registerBeanDefinition方法中实现。这个时候就会调用我们刚才提到的spring默认的容器DefaultListableBeanFactory的实现了。在方法中会先尝试根据beanName获取对应的beanDefinition,如果已经存在就会抛异常。然后就判断是否已经开始创建bean了就锁住beanDefinitionMap更新beanDefinition,否则就直接存入beanDefinitionMap。代码如下
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException {
Assert.hasText(beanName, "Bean name must not be empty");
Assert.notNull(beanDefinition, "BeanDefinition must not be null");
if (beanDefinition instanceof AbstractBeanDefinition) {
try {
((AbstractBeanDefinition) beanDefinition).validate();
}
catch (BeanDefinitionValidationException ex) {
throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
"Validation of bean definition failed", ex);
}
}
// 尝试根据beanName从beanDefinitionMap获取BeanDefinition
BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
// 如果已经存在
if (existingDefinition != null) {
if (!isAllowBeanDefinitionOverriding()) {
throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
"Cannot register bean definition [" + beanDefinition + "] for bean '" + beanName +
"': There is already [" + existingDefinition + "] bound.");
}
// 下面的省略
// ...............
this.beanDefinitionMap.put(beanName, beanDefinition);
}
else {
// 如果bean已经开始创建
if (hasBeanCreationStarted()) {
// Cannot modify startup-time collection elements anymore (for stable iteration)
// 容器上锁
synchronized (this.beanDefinitionMap) {
// 更新beanDefinition
this.beanDefinitionMap.put(beanName, beanDefinition);
// 加入updatedDefinitions列表
List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
updatedDefinitions.addAll(this.beanDefinitionNames);
updatedDefinitions.add(beanName);
this.beanDefinitionNames = updatedDefinitions;
// 更新完成
if (this.manualSingletonNames.contains(beanName)) {
Set<String> updatedSingletons = new LinkedHashSet<>(this.manualSingletonNames);
updatedSingletons.remove(beanName);
this.manualSingletonNames = updatedSingletons;
}
}
}
else {
// 直接存入容器
// Still in startup registration phase
this.beanDefinitionMap.put(beanName, beanDefinition);
this.beanDefinitionNames.add(beanName);
this.manualSingletonNames.remove(beanName);
}
this.frozenBeanDefinitionNames = null;
}
if (existingDefinition != null || containsSingleton(beanName)) {
resetBeanDefinition(beanName);
}
else if (isConfigurationFrozen()) {
clearByTypeCache();
}
}
hasBeanCreationStarted是干嘛的
hasBeanCreationStarted顾名思义就是判断是否已经开始创建bean了。大家都知道spring留的拓展点无处不在,我们随时可以通过容器的api随时往里面注册beanDefinition,虽然beanDefinitionMap默认是ConcurrentHashMap容器,但更新beanDefinition修改的不只是beanDefinitionMap,在修改的时候不能保障其他的业务的线程安全。所以需要加锁。当然我们这次演示是正常流程线性走完的,默认是直接this.beanDefinitionMap.put(beanName, beanDefinition)的,到这里一个BeanDefinition的注册流程已经走完了。
总结
本篇文章说的是如何从xml文件中加载beanDefinition的,在spring中有许多加载bean的方式。每种方式的具体实现不同,但基本上都是把信息封装成beanDefinition最后调用registerBeanDefinition进行注册。注解扫描的加载方式是通过beanFactoryPostProcessor拓展点完成的,感兴趣的可以看看beanFactoryPostProcessor后置处理器。