java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Spring BeanDefinition父子关系

Spring BeanDefinition父子关系示例解析

作者:江南一点雨

这篇文章主要为大家介绍了Spring BeanDefinition父子关系示例解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

引言

在 Spring 框架中,BeanDefinition 是一个核心概念,用于定义和配置 bean 的元数据,虽然在实际应用中,我们一般并不会或者很少直接定义 BeanDefinition,但是,我们在 XML 文件中所作的配置,以及利用 Java 代码做的各种 Spring 配置,都会被解析为 BeanDefinition,然后才会做进一步的处理。BeanDefinition 允许开发人员以一种声明性的方式定义和组织 bean,这里有很多属性,今天松哥单纯的来和小伙伴们聊一聊它的 parentName 属性,parentName 属性在 BeanDefinition 中扮演着重要的角色,用于建立 bean 之间的父子关系。

之前有一篇文章和小伙伴们聊了 BeanFactory 之间的父子关系(Spring 中的父子容器是咋回事?),大家注意和今天的内容进行区分,今天我们聊的是 BeanDefinition 之间的父子关系。

BeanDefinition 的 parentName 属性的主要功能是允许我们在创建一个 bean 的同时,能够继承另一个已经定义好的 bean。通过指定 parentName 属性,我们可以重用已有 bean 的配置,并在此基础上进行修改或扩展。

先不废话了,我先来举两个例子,小伙伴们先感受一下 BeanDefinition 的作用。

1. 实践

假设我有如下两个类,首先是一个动物的基类,如下:

public class Animal {
    private String name;
    private Integer age;
    //省略 getter/setter
}

然后有一个 Dog 类,如下:

public class Dog {
    private String name;
    private Integer age;
    private String color;
    //省略 getter/setter
}

 小伙伴们注意,这里的 Dog 类并没有继承自 Animal 类,但是有两个跟 Animal 同名的属性。之所以这样设计是希望小伙伴们理解 BeanDefinition 中的 parentName 属性和 Java 中的继承并无关系,虽然大部分情况下我们用到 parentName 的时候,Java 中相关的类都是继承关系。

现在,有一些通用的属性我想在 Animal 中进行配置,Dog 中特有的属性则在 Dog 中进行配置,我们来看下通过 XML 和 Java 分别该如何配置。

1.1 XML 配置

<bean id="animal" class="org.javaboy.demo.p2.Animal">
    <property name="name" value="小黑"/>
    <property name="age" value="3"/>
</bean>
<bean class="org.javaboy.demo.p2.Dog" id="dog" parent="animal">
    <property name="color" value="黑色"/>
</bean>

小伙伴们看到,首先我们配置 Animal,Animal 中有 name 和 age 两个属性,然后我又配置了 Dog Bean,并未之指定了 parent 为 animal,然后给 Dog 设置了 color 属性。

现在,Dog Bean 定义出来的 BeanDefinition 中将来就包含了 animal 中的属性值。

1.2 Java 配置

再来看看 Java 配置该如何写。

AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
RootBeanDefinition pbd = new RootBeanDefinition();
MutablePropertyValues pValues = new MutablePropertyValues();
pValues.add("name", "小黄");
pbd.setBeanClass(Animal.class);
pbd.setPropertyValues(pValues);
GenericBeanDefinition cbd = new GenericBeanDefinition();
cbd.setBeanClass(Dog.class);
cbd.setParentName("parent");
MutablePropertyValues cValues = new MutablePropertyValues();
cValues.add("name", "小强");
cbd.setPropertyValues(cValues);
ctx.registerBeanDefinition("parent", pbd);
ctx.registerBeanDefinition("child", cbd);
ctx.refresh();
Dog child = (Dog) ctx.getBean("child");
System.out.println("child = " + child);

这里我使用了 RootBeanDefinition 来做 parent,其实从名字上就能看出来 RootBeanDefinition 适合做 parent,并且 RootBeanDefinition 不能作为 child。强行设置运行时会抛出异常,RootBeanDefinition#setParentName 方法如下:

@Override
public void setParentName(@Nullable String parentName) {
    if (parentName != null) {
        throw new IllegalArgumentException("Root bean cannot be changed into a child bean with parent reference");
    }
}

MutablePropertyValues 是为相应的对象设置属性值。

child 我这里使用了 GenericBeanDefinition,这个主要是做 child 的处理,最早有一个专门做 child 的 ChildBeanDefinition,不过自从 Spring2.5 开始提供了 GenericBeanDefinition 之后,现在用来做 child 首选 GenericBeanDefinition。

在上述案例中,parent 和 child 都设置了 name 属性,那么 child 会覆盖掉 parent,这一点和 Java 中的继承一致。

用法就是这样,并不难。

这就是 Spring BeanDefinition 中的父子关系问题。

2. 源码分析

那么接下来我们也把这块的源码稍微来分析一下。

简便起见,我们就不从 Bean 的创建开始分析了,直接来看和 BeanDefinition 中 parentName 属性相关的地方,但是前面涉及到的方法还是给小伙伴们梳理一下,就是下图:

那么这里涉及到的关键方法其实就是 AbstractBeanFactory#getMergedBeanDefinition:

protected RootBeanDefinition getMergedBeanDefinition(
        String beanName, BeanDefinition bd, @Nullable BeanDefinition containingBd)
        throws BeanDefinitionStoreException {
    synchronized (this.mergedBeanDefinitions) {
        RootBeanDefinition mbd = null;
        RootBeanDefinition previous = 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 || mbd.stale) {
            previous = mbd;
            if (bd.getParentName() == null) {
                // Use copy of given root bean definition.
                if (bd instanceof RootBeanDefinition rootBeanDef) {
                    mbd = rootBeanDef.cloneBeanDefinition();
                }
                else {
                    mbd = new RootBeanDefinition(bd);
                }
            }
            else {
                // Child bean definition: needs to be merged with parent.
                BeanDefinition pbd;
                try {
                    String parentBeanName = transformedBeanName(bd.getParentName());
                    if (!beanName.equals(parentBeanName)) {
                        pbd = getMergedBeanDefinition(parentBeanName);
                    }
                    else {
                        if (getParentBeanFactory() instanceof ConfigurableBeanFactory parent) {
                            pbd = parent.getMergedBeanDefinition(parentBeanName);
                        }
                        else {
                            throw new NoSuchBeanDefinitionException(parentBeanName,
                                    "Parent name '" + parentBeanName + "' is equal to bean name '" + beanName +
                                            "': cannot be resolved without a ConfigurableBeanFactory parent");
                        }
                    }
                }
                // Deep copy with overridden values.
                mbd = new RootBeanDefinition(pbd);
                mbd.overrideFrom(bd);
            }
            // Set default singleton scope, if not configured before.
            if (!StringUtils.hasLength(mbd.getScope())) {
                mbd.setScope(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());
            }
            // Cache the merged bean definition for the time being
            // (it might still get re-merged later on in order to pick up metadata changes)
            if (containingBd == null && isCacheBeanMetadata()) {
                this.mergedBeanDefinitions.put(beanName, mbd);
            }
        }
        if (previous != null) {
            copyRelevantMergedBeanDefinitionCaches(previous, mbd);
        }
        return mbd;
    }
}

这个方法看名字就是要获取一个合并之后的 BeanDefinition,就是将 child 中的属性和 parent 中的属性进行合并,然后返回,这个方法中有一个名为 mbd 的变量,这就是合并之后的结果。

核心流程就是上面这个步骤,如此之后,拿到手的就是和 parent 合并之后的 BeanDefinition 了。

3. 小结

最后我们再来稍微总结下:

使用 parentName 属性的一个主要优势是提高代码的可维护性和重用性。当我们需要创建多个相似的 bean 时,可以通过定义一个基础 bean,并在其他 bean 中使用 parentName 属性来继承其配置。这样,我们只需在基础 bean 中定义一次配置,而不必为每个派生 bean 重复相同的配置。

另一个使用 parentName 属性的场景是在多个层次结构中定义 bean。假设我们有一个通用的基础服务层 bean,而不同的业务模块需要在此基础上进行扩展。通过使用 parentName 属性,我们可以为每个业务模块定义一个派生 bean,并在其中添加特定于模块的配置。这种层次结构的定义使得我们可以更好地组织和管理不同模块之间的 bean。

通过使用 parentName 属性,我们可以轻松地创建和管理 bean 的层次结构。这种继承关系使得我们可以更好地组织和重用 bean 的配置,减少了代码的冗余性。同时,它还提供了一种灵活的方式来定义不同模块之间的 bean,使得应用程序更易于扩展和维护。

综上所述,Spring 框架中的 BeanDefinition 的 parentName 属性允许我们在定义 bean 时建立父子关系,从而提高代码的可维护性和重用性。通过继承已有 bean 的配置,我们可以避免重复编写相似的配置,并更好地组织和管理不同层次结构的 bean。

有的小伙伴们可能会搞混今天内容和之前松哥所写的 Spring 父子容器之间的关系,小伙伴们参考这篇文章就清楚啦:Spring 中的父子容器是咋回事?

另外,Spring BeanDefinition 中的 parentName 和 Java 中的继承虽然有点像,但是并不能同等看待,它们之间也还是有一些区别的:

好啦,Spring BeanDefinition 中的 parentName 属性现在大家明白了吧~

以上就是Spring BeanDefinition父子关系示例解析的详细内容,更多关于Spring BeanDefinition父子关系的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文