Does Spring BeanDefinition also have a parent-child relationship?

In the Spring framework, BeanDefinition is a core concept used to define and configure bean metadata. Although in actual applications, we generally do not or rarely directly define BeanDefinition, however, the configuration we make in the XML file, As well as various Spring configurations made using Java code, they will be parsed into BeanDefinitions and then further processed. BeanDefinition allows developers to define and organize beans in a declarative way. There are many attributes here. Today, Brother Song will simply talk to you about its parentName attribute. The parentName attribute plays an important role in BeanDefinition. Used to establish parent-child relationships between beans.

Previously, Brother Song had an article and talked with friends about the father-son relationship between BeanFactory ( 

What is the father-son container in Spring? ). Please pay attention to distinguish it from today’s content. Today we are talking about the father-son relationship between BeanDefinition. relation.

The main function of the parentName attribute of BeanDefinition is to allow us to inherit another defined bean while creating a bean. By specifying the parentName attribute, we can reuse the configuration of an existing bean and modify or extend it.

Without further ado, let me give you two examples to let you feel the role of BeanDefinition.

1. Practice

Suppose I have the following two classes. The first is an animal base class, as follows:

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

Then there is a Dog class, as follows:

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

Friends, please note that the Dog class here does not inherit from the Animal class, but has two properties with the same name as Animal. The reason for this design is that we hope that friends will understand that the parentName attribute in BeanDefinition has nothing to do with inheritance in Java. Although in most cases when we use parentName, the related classes in Java are inheritance relationships.

Now, there are some common properties that I want to configure in Animal, and properties specific to Dog are configured in Dog. Let’s take a look at how to configure them through XML and Java respectively.

1.1 XML configuration

< 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 = "black" /> 
</ bean >

Friends, you can see that first we configure Animal, which has two attributes: name and age. Then I configure Dog Bean without specifying parent as animal, and then set the color attribute for Dog.

Now, the BeanDefinition defined by Dog Bean will include the attribute values ​​​​in animal in the future.

1.2 Java configuration

Let’s take a look at how to write Java configuration.

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" , "Xiaoqiang" );
cbd.setPropertyValues(cValues);
ctx.registerBeanDefinition( "parent" , pbd);
ctx.registerBeanDefinition( "child" , cbd);
ctx.refresh();
Dog  child  = (Dog) ctx.getBean( "child" );
System.out.println( "child = " + child);

Here I use RootBeanDefinition as parent. In fact, it can be seen from the name that RootBeanDefinition is suitable for parent, and RootBeanDefinition cannot be used as child. Forcibly setting the runtime will throw an exception. The RootBeanDefinition#setParentName method is as follows:

@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 ​​sets property values ​​for the corresponding objects.

child I use GenericBeanDefinition here, which is mainly used for child processing. There was a ChildBeanDefinition specifically for child at first, but since Spring 2.5 started to provide GenericBeanDefinition, GenericBeanDefinition is now the first choice for child.

In the above case, both parent and child have the name attribute set, so the child will override the parent, which is consistent with inheritance in Java.

That’s how to use it, it’s not difficult.

This is the parent-child relationship issue in Spring BeanDefinition.

2. Source code analysis

Then let’s also analyze the source code of this piece a little bit.

For the sake of simplicity, we will not start the analysis from the creation of the Bean, but directly look at the places related to the parentName attribute in the BeanDefinition. However, the methods involved above are still sorted out for the friends, as shown in the figure below:

So the key method involved here is actually AbstractBeanFactory#getMergedBeanDefinition:

protectedRootBeanDefinition 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);
        }
        returnmbd ;
    }
}

Judging from the name, this method is to obtain a merged BeanDefinition, which is to merge the attributes in the child with the attributes in the parent, and then return. There is a variable named mbd in this method, which is the result of the merger.

  1. First, it will try to obtain the merged BeanDefinition from the mergedBeanDefinitions variable. mergedBeanDefinitions is equivalent to a temporary cache. If it has been obtained before, it will be saved to mergedBeanDefinitions after the acquisition is successful. If this is the first time to enter this method , then there is no data we need in this variable, so we will continue to perform the following steps.
  2. When the mbd is not obtained in step 1, continue to judge bd.getParentName()whether it is empty. This is actually to check whether the current BeanDefinition has set parentName. If it is set, the value obtained here is not null, otherwise it is null. If the value obtained here is null, then an mbd will be generated based on the currently passed in BeanDefinition. As for the specific generation method: if the passed in BeanDefinition is of RootBeanDefinition type, the clone method is called to generate mbd (essentially also new A new RootBeanDefinition), if the incoming BeanDefinition is not of RootBeanDefinition type, directly create a new RootBeanDefinition. During the new process, all the properties on the incoming BeanDefinition will be copied to the new RootBeanDefinition.
  3. If bd.getParentName()it is not empty, it means that there is a parent BeanDefinition, so it needs to be merged. There is another small detail when merging. If parentBeanName is equal to the current beanName, since Spring does not allow beans with the same name to exist in the same container, so This means that parentBeanName may be the Bean of the parent container. At this time, it needs to be processed in the parent container. Of course, the current method is still called in the end. Regarding the parent-child container, friends can refer to Song Ge’s previous article on Father and Child in Spring. What happened to the container? One article. If parentBeanName is not equal to the current beanName, you can now call the getMergedBeanDefinition method to obtain the parentBeanDefinition. getMergedBeanDefinition is an overloaded method of the current method. This method will eventually call the current method because parentBeanDefinition itself may also have a parentBeanDefinition.
  4. After having pbd, create a new RootBeanDefinition, and then call the overrideFrom method to merge attributes. The method of merging is to use the attributes in the incoming BeanDefinition to overwrite the attributes with the same name in pbd.
  5. The last step is to set the scope attribute, etc., and then return mbd.

The core process is the above step. After this, what you get is the BeanDefinition after merging with the parent.

3. Summary

Finally, let’s summarize a little bit:

A major advantage of using the parentName attribute is increased code maintainability and reusability. When we need to create multiple similar beans, we can inherit its configuration by defining a base bean and using the parentName attribute in other beans. This way, we only need to define the configuration once in the base bean instead of repeating the same configuration for each derived bean.

Another scenario where the parentName attribute is used is when beans are defined in multiple hierarchies. Suppose we have a common basic service layer bean, and different business modules need to be extended on this basis. By using the parentName attribute, we can define a derived bean for each business module and add module-specific configuration in it. The definition of this hierarchical structure allows us to better organize and manage beans between different modules.

By using the parentName attribute, we can easily create and manage bean hierarchies. This inheritance relationship allows us to better organize and reuse bean configurations, reducing code redundancy. At the same time, it also provides a flexible way to define beans between different modules, making the application easier to expand and maintain.

To sum up, the parentName attribute of BeanDefinition in the Spring framework allows us to establish a parent-child relationship when defining beans, thereby improving the maintainability and reusability of the code. By inheriting the configuration of existing beans, we can avoid repeatedly writing similar configurations and better organize and manage beans in different hierarchies.

Some friends may be confused about the relationship between today’s content and the Spring parent-child container written by Brother Song before. You can find out by referring to this article: What is the parent-child container in Spring? .

In addition, although parentName in Spring BeanDefinition is somewhat similar to inheritance in Java, they cannot be treated equally. There are still some differences between them:

  1. Concept and role: Inheritance in Java is an object-oriented programming concept that is used to define the parent-child relationship between classes. Subclasses inherit the properties and methods of the parent class. In Spring, the parentName attribute of BeanDefinition is used to define the parent-child relationship between beans. A derived bean can inherit the configuration of another defined bean.
  2. Syntax and usage: In Java, inheritance is extendsachieved by using the keyword, and subclasses obtain the properties and methods of the parent class by inheriting the parent class. In Spring, you specify the parent bean of a bean by configuring the parentName attribute in the BeanDefinition, thereby inheriting the configuration of the parent bean.
  3. Scope and application: Inheritance in Java is mainly used for the inheritance relationship of classes to define the hierarchy between classes and the reuse of code. In Spring, the inheritance of BeanDefinition is mainly used to define the configuration inheritance relationship between beans, to organize and manage the configuration of beans, and to improve the maintainability and reusability of the code.

Okay, now everyone understands the parentName attribute in Spring BeanDefinition~