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

版权声明:如有转载,请标明出处,谢谢合作! https://blog.csdn.net/lyc_liyanchao/article/details/82585752

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

猜你喜欢

转载自blog.csdn.net/lyc_liyanchao/article/details/82585752