关于spring:Spring-BeanDefinition-也分父子关系

3次阅读

共计 7218 个字符,预计需要花费 19 分钟才能阅读完成。

在 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 的变量,这就是合并之后的后果。

  1. 首先会尝试从 mergedBeanDefinitions 变量中获取到合并之后的 BeanDefinition,mergedBeanDefinitions 相当于就是一个长期缓存,如果之前曾经获取过了,那么获取胜利之后就将之保留到 mergedBeanDefinitions 中,如果是第一次进入到该办法中,那么该变量中就没有咱们须要的数据,所以会继续执行前面的步骤。
  2. 当第 1 步并未拿到 mbd 的时候,接下来持续判断 bd.getParentName() 是否为空,这个其实就是查看以后的 BeanDefinition 是否有设置 parentName,如果有设置,这里获取到的就不为 null,否则为 null。如果这里获取到的值为 null,那么就会依据以后传入的 BeanDefinition 生成一个 mbd,至于具体的生成形式:如果传入的 BeanDefinition 是 RootBeanDefinition 类型的,则调用 clone 办法去生成 mbd(实质上也是 new 一个新的 RootBeanDefinition),如果传入的 BeanDefinition 不是 RootBeanDefinition 类型的,则间接 new 一个新的 RootBeanDefinition,在 new 的过程中,会把传入的 BeanDefinition 上的属性都复制到新的 RootBeanDefinition 中。
  3. 如果 bd.getParentName() 不为空,则意味着存在 parent BeanDefinition,所以就要进行合并解决了,合并时候又有一个小细节,如果 parentBeanName 等于以后的 beanName,因为 Spring 在同一个容器中不容许存在同名的 bean,所以这就阐明 parentBeanName 可能是父容器的 Bean,此时就要去父容器中去解决,当然最终调用到的还是以后办法,对于父子容器这一块,小伙伴们能够参考松哥之前的 Spring 中的父子容器是咋回事?一文。如果 parentBeanName 不等于以后 beanName,那么当初就能够调用 getMergedBeanDefinition 办法去获取到 parentBeanDefinition 了,getMergedBeanDefinition 是以后办法的重载办法,该办法最终也会调用到以后办法,起因就在于 parentBeanDefinition 自身也可能存在 parentBeanDefinition。
  4. 有了 pbd 之后,接下来 new 一个 RootBeanDefinition,而后调用 overrideFrom 办法进行属性合并,合并的形式就是用传入的 BeanDefinition 中的属性去笼罩 pbd 中同名的属性。
  5. 最初就是再设置 scope 属性等,而后把 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 中的继承尽管有点像,然而并不能等同对待,它们之间也还是有一些区别的:

  1. 概念和作用:Java 中的继承是一种面向对象的编程概念,用于定义类之间的父子关系,子类继承父类的属性和办法。而在 Spring 中,BeanDefinition 的 parentName 属性是用于定义 bean 之间的父子关系,一个派生 bean 能够继承另一个已定义的 bean 的配置。
  2. 语法和用法:在 Java 中,继承是通过应用关键字 extends 来实现的,子类通过继承父类来取得父类的属性和办法。而在 Spring 中,通过在 BeanDefinition 中配置 parentName 属性来指定一个 bean 的父 bean,从而继承父 bean 的配置。
  3. 范畴和利用:Java 中的继承次要用于类的继承关系,用于定义类之间的层次结构和代码的重用。而在 Spring 中,BeanDefinition 的继承次要用于定义 bean 之间的配置继承关系,用于组织和治理 bean 的配置,进步代码的可维护性和重用性。

好啦,Spring BeanDefinition 中的 parentName 属性当初大家明确了吧~

正文完
 0