17--Spring创建Bean的准备工作(二),合并父bean属性和解析depends-on标签
前一小节分析了Spring从缓存中加载bean的过程,对于单例bean,如果缓存中不存在的话,那么就要重新创建一个新的bean实例,在真正创建bean实例之前,还需要做很多预处理工作,本篇就来分析创建bean之前的准备工作。
- 判断指定的原型模式的bean是否当前正在创建(在当前线程内)
- 如果当前BeanFactory不包含bean的定义,则尝试从父BeanFactory中加载bean
- 如果当前bean不是用于类型检查,则将该bean标记为已经被创建或者即将被创建
- 合并beanDefinition
- 预加载depend-on标签所依赖的bean,保证依赖bean先被初始化
之前的章节中我们已经新建了很多bean,但是为每个章节都能单独给读者阅读,我们还是要新建一bean和配置文件,这样也能方便读者阅读!
我们先来看一下Spring如果无法从缓存中或取bean之后的处理代码片段:
// 如果未能从缓存中获取到bean,则要重新创建bean
else {
// Fail if we're already creating this bean instance: We're assumably within a circular reference.
// ① 判断指定的原型模式的bean是否当前正在创建(在当前线程内),如果是->则抛出异常(Spring不会解决原型模式bean的循环依赖)
if (isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
// Check if bean definition exists in this factory.
// ② 检测bean definition是否存在beanFactory中
BeanFactory parentBeanFactory = getParentBeanFactory();
// 如果当前BeanFactory中不包含给定beanName的beanDefinition定义,且父beanFactory不为空,则去父beanFactory中再次查找
if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
// 将name转换为原始beanName
// 因为这里的name已经经过beanName的规范处理,例如:&myBean-->规范-->myBean
// 所以当我们再次去父beanFactory查找时,要将beanName再次转换为原始的beanName,myBean-->回转-->&myBean
String nameToLookup = originalBeanName(name);
// 下面会递归调用各种getBean的方法重载,从当前bean的父factoryBean中加载bean
if (parentBeanFactory instanceof AbstractBeanFactory) {
return ((AbstractBeanFactory) parentBeanFactory).doGetBean(nameToLookup, requiredType, args, typeCheckOnly);
}
else if (args != null) {
// 参数不为空,则委托parentBeanFactory使用显式参数调动
return (T) parentBeanFactory.getBean(nameToLookup, args);
}
else if (requiredType != null) {
// 参数为空,则委托parentBeanFactory使用标准的getBean方法获取bean
return parentBeanFactory.getBean(nameToLookup, requiredType);
}
else {
// 否则委托parentBeanFactory使用默认的getBean方法
return (T) parentBeanFactory.getBean(nameToLookup);
}
}
// ③ 如果当前bean不是用于类型检查,则将该bean标记为已经被创建或者即将被创建
if (!typeCheckOnly) {
markBeanAsCreated(beanName);
}
try {
// ④ 合并beanDefinition,如果指定的bean是一个子bean的话,则遍历其所有的父bean
final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
// 校验合并的beanDefinition,如果验证失败,则抛出异常
checkMergedBeanDefinition(mbd, beanName, args);
// ⑤ 确保初始化当前bean所依赖的bean。
String[] dependsOn = mbd.getDependsOn();
if (dependsOn != null) {
// 循环所有的依赖bean,并递归实例化
for (String dep : dependsOn) {
if (isDependent(beanName, dep)) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
}
// 注册依赖
registerDependentBean(dep, beanName);
try {
// 实例化依赖的bean
getBean(dep);
}
catch (NoSuchBeanDefinitionException ex) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"'" + beanName + "' depends on missing bean '" + dep + "'", ex);
}
}
}
// 省略与本节无关的代码...
}
通过注释可以看到,流程还是比较清晰的,其中第①②③步的代码比较简单,这里不再做过多的分析(其中涉及到的bean的循环依赖问题,我们会在之后的章节中详细讲解
),本章着重分析第④和第⑤步的操作!
1. 合并beanDefinition,Spring中的父子bean
与Java中的继承机制相似,Spring中也存在bean的继承。这就是Spring中的父子bean,父子bean统一定义Spring Bean的公共属性、作业范围scope,并避免了冗余和修改的繁琐。而且在使用上,父子bean与普通的bean也有一定的区别
- 子bean必须与父bean保持兼容,也就是说子bean中必须有父bean定义的所有属性
- 父bean必须是抽象bean,也就是不让bean工厂实例化该bean,因为我们只是希望将父bean作为模板来使用,所以无需实例化该bean
- 父bean可以不指定class属性,此时的父bean完全当做模板来使用,但是子类必须指定class属性
接下来通过一个简单的例子,演示一下父子Bean的使用,并从源码角度分析,Spring是如何合并beanDefinition的
- 新建ParentBean作为父bean
package com.lyc.cn.day08;
/**
* 定义父bean
* @author: LiYanChao
* @create: 2018-09-07 16:36
*/
public class ParentBean {
/** 姓名 **/
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "ParentBean{" + "name='" + name + '\'' + '}';
}
}
- 新建SunBean作为子bean
package com.lyc.cn.day08;
/**
* 定义子bean,注意这里SunBean和ParentBean之间无继承关系,
* 而是通过配置文件维护其父子关系
* @author: LiYanChao
* @create: 2018-09-07 16:36
*/
public class SunBean {
/** 姓名 **/
private String name;
/** 年龄 **/
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "SunBean [name=" + name + ", age=" + age + "]";
}
}
- 新建day08.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:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- ====================父子bean==================== -->
<!-- 子bean必须与父bean保持兼容,也就是说子bean中必须有父bean定义的所有属性 -->
<!-- 父bean必须是抽象bean,也就是不让bean工厂实例化该bean-->
<!-- 父bean可以不指定class属性,此时的父bean完全当做模板来使用,但是子类必须指定class属性-->
<bean id="parentBean" abstract="true">
<property name="name" value="我是父亲"/>
</bean>
<bean id="sunBean" class="com.lyc.cn.day08.SunBean" parent="parentBean">
<property name="age" value="18"/>
</bean>
</beans>
- 新建MyTest测试类
package com.lyc.cn.day08;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.io.ClassPathResource;
import java.util.ArrayList;
import java.util.List;
/**
* @author: LiYanChao
* @create: 2018-09-07 16:43
*/
public class MyTest {
private XmlBeanFactory xmlBeanFactory;
@Before
public void initXmlBeanFactory() {
System.out.println("========测试方法开始=======\n");
xmlBeanFactory = new XmlBeanFactory(new ClassPathResource("day08.xml"));
}
@After
public void after() {
System.out.println("\n========测试方法结束=======");
}
/**
* 父子bean测试
*/
@Test
public void test1() {
SunBean sunBean = xmlBeanFactory.getBean("sunBean", SunBean.class);
System.out.println(sunBean.toString());
}
}
- 运行测试用例
========测试方法开始=======
SunBean [name=我是父亲, age=18]
========测试方法结束=======
以上的代码非常简单,在配置文件了定义了一个父bean,注入name属性为‘我是父亲’,子bean中只注入了一个age属性,但是通过运行的结果可以看到,子bean已经继承了父bean中属性,所以如果大家在开发中需要有模板bean的话,父子bean是个不错的选择!接下来我们分析下源码,看看Spring是如何合并父子bean的属性定义的。
2. 合并beanDefinition代码解析
Spring合并beanDefinition的过程,大概可以分为以下几个步骤:
- 优先从缓存中加载合并的beanDefinition
- 如果未能从缓存中加载到,则递归执行所有父bean的beanDefinition的合并操作
- 如果beanDefinition没有定义bean作用域信息的话,则默认设置为单例模式
- 暂时缓存合并的bean定义(为了获取元数据更改,以后可能还会重新合并)
// 合并beanDefinition,如果指定的bean是一个子bean的话,则遍历其所有的父bean
final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
// 校验合并的beanDefinition,如果验证失败,则抛出异常
checkMergedBeanDefinition(mbd, beanName, args);
以上面的代码为入口,我们打开依次打开getMergedLocalBeanDefinition(String beanName)方法:
protected RootBeanDefinition getMergedLocalBeanDefinition(String beanName) throws BeansException {
// Quick check on the concurrent map first, with minimal locking.
// 优先从缓存中加载合并的beanDefinition
RootBeanDefinition mbd = this.mergedBeanDefinitions.get(beanName);
if (mbd != null) {
return mbd;
}
// 未能从缓存中加载合并的beanDefinition,则合并一个新的beanDefinition
return getMergedBeanDefinition(beanName, getBeanDefinition(beanName));
}
再来看一下合并beanDefinition的代码:
protected RootBeanDefinition getMergedBeanDefinition(String beanName, BeanDefinition bd, @Nullable BeanDefinition containingBd)
throws BeanDefinitionStoreException {
synchronized (this.mergedBeanDefinitions) {
RootBeanDefinition mbd = null;
// Check with full lock now in order to enforce the same merged instance.
if (containingBd == null) {
mbd = this.mergedBeanDefinitions.get(beanName);
}
if (mbd == null) {
// 如果父bean为null,无需合并,则直接克隆或创建一个RootBeanDefinition实例
if (bd.getParentName() == null) {
// 当前BeanDefinition是RootBeanDefinition的实例,则克隆
if (bd instanceof RootBeanDefinition) {
mbd = ((RootBeanDefinition) bd).cloneBeanDefinition();
}
// 否则,创建一个新的RootBeanDefinition
else {
mbd = new RootBeanDefinition(bd);
}
}
else {
// 父bean存在,需要合并属性
BeanDefinition pbd;
try {
// 规范父bean名称
String parentBeanName = transformedBeanName(bd.getParentName());
// 如果当前beanName不等于parentBeanName,则递归执行所有的父bean的beanDefinition合并
if (!beanName.equals(parentBeanName)) {
pbd = getMergedBeanDefinition(parentBeanName);
}
else {
BeanFactory parent = getParentBeanFactory();
if (parent instanceof ConfigurableBeanFactory) {
pbd = ((ConfigurableBeanFactory) parent).getMergedBeanDefinition(parentBeanName);
}
else {
throw new NoSuchBeanDefinitionException(parentBeanName,
"Parent name '" + parentBeanName + "' is equal to bean name '" + beanName +
"': cannot be resolved without an AbstractBeanFactory parent");
}
}
}
catch (NoSuchBeanDefinitionException ex) {
throw new BeanDefinitionStoreException(bd.getResourceDescription(), beanName,
"Could not resolve parent bean definition '" + bd.getParentName() + "'", ex);
}
// 深度复制beanDefinition
mbd = new RootBeanDefinition(pbd);
mbd.overrideFrom(bd);
}
// 如果beanDefinition没有定义bean作用域信息的话,则默认设置为单例模式
if (!StringUtils.hasLength(mbd.getScope())) {
mbd.setScope(RootBeanDefinition.SCOPE_SINGLETON);
}
// A bean contained in a non-singleton bean cannot be a singleton itself.
// Let's correct this on the fly here, since this might be the result of
// parent-child merging for the outer bean, in which case the original inner bean
// definition will not have inherited the merged outer bean's singleton status.
if (containingBd != null && !containingBd.isSingleton() && mbd.isSingleton()) {
mbd.setScope(containingBd.getScope());
}
// 暂时缓存合并的bean定义(为了获取元数据更改,以后可能还会重新合并)
if (containingBd == null && isCacheBeanMetadata()) {
this.mergedBeanDefinitions.put(beanName, mbd);
}
}
return mbd;
}
}
合并过程还是比较简单的,其中的containingBd参数的作用本人也没有推断出其作用,希望了解的同学可以评论或私信我!
2. 初始化depends-on依赖
depends-on的作用是当实例化一个bean是,保证该Bean所依赖的其他bean已经初始化。Spring对depends-on的处理比较简单,我们就贴出代码不举例说明了,大体上分为两步做处理
- 获取bean的所有依赖bean
- 循环获取到的依赖bean,递归处理,如果依赖bean还有其他依赖的话。
// 确保初始化当前bean所依赖的bean。
String[] dependsOn = mbd.getDependsOn();
if (dependsOn != null) {
// 循环所有的依赖bean,并递归实例化
for (String dep : dependsOn) {
if (isDependent(beanName, dep)) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
}
// 注册依赖
registerDependentBean(dep, beanName);
try {
// 实例化依赖的bean
getBean(dep);
}
catch (NoSuchBeanDefinitionException ex) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"'" + beanName + "' depends on missing bean '" + dep + "'", ex);
}
}
}
本篇的分析就到此了,但是有一点需要注意,Spring是不能解决depends-on的循环依赖的,例如:
<bean id="cat" class="com.lyc.cn.day08.Cat" p:name="美美" depends-on="dog"/>
<bean id="dog" class="com.lyc.cn.day08.Dog" p:name="壮壮" depends-on="cat"/>
这样的配置是会引起Circular depends-on
错误的!
上一篇: Spring学习 — 自动装配