欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

17--Spring创建Bean的准备工作(二),合并父bean属性和解析depends-on标签

程序员文章站 2022-05-24 14:45:08
...

前一小节分析了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错误的!