On the relationship among BeanDefinition, BeanDefinitionMap and rootbeandefinition

Keywords: Java Spring mvc

Through this article, you will gain:

  1. The relationship among rootbeandefinition, BeanDefinition and BeanDefinitionMap is its basic meaning

  2. Implementation subclass of BeanDefinition interface

  3. Meaning of beanClass member variable in BeanDefinition subclass

  4. Process for obtaining rootbeandefinition according to beanName




1, BeanDefinition

BeanDefinition: an interface defined by a Bean. In Spring, the interface has many implementation classes, as shown in the figure below. Different implementation classes of BeanDefinition also represent different meanings.



1. Concrete implementation subclass

  1. If we start the Spring container by configuring the class, we will generate AnnotatedGenericBeanDefinition
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

  1. Before instantiating a Bean, Spring needs to load some of its internal beans, such as the Bean corresponding to @ Configuration. At this time, the RootBeanDefinition is used

  2. When we scan beans through the package path, the scanned Bean definition uses the ScannedGenericBeanDefinition


Observe the above three situations:

  1. Initialize the configuration class as an implementation class of BeanDefinition
  2. Initialize the class corresponding to the @ Configuration annotation to an implementation class of BeanDefinition
  3. Initialize the scanned classes that meet the requirements of the Bean as an implementation class of the BeanDefinition




2. Manually create subclasses

Similarly, can we also add BeanDefinition to the Spring container?

So you can see the following code steps, which means that we manually create an implementation class of BeanDefinition and add it to the Spring container:

  1. Create a BeanDefinition
  2. Sets the type of BeanDefinition
  3. Add BeanDefinition to the Spring container




3. Meaning of beanClass member variable

Click the code of AbstractBeanDefinition Class, and we will find that its beanClass type is not a Class type, but an Object type. According to the previous Teacher Class description, this attribute is used to mark the type of the corresponding Bean. Is it not good to use the Class type directly?

@Nullable
private volatile Object beanClass;

In fact, the beanClass attribute starts with the full path name of a Class of String type. Later, when initializing, the Spring container will verify whether the attribute is a Class or a String. If it is a String, resolve it to a Class.



The corresponding location for setting this field is:

The following code comes from the construction method of the ScannedGenericBeanDefinition class initialized when we scan qualified beans under the package. Pass in the metadata information of the class to this method. Small partners who are not familiar with the class metadata can refer to: Simple use of MetadataReader, ClassMetadata and AnnotationMetadata

public ScannedGenericBeanDefinition(MetadataReader metadataReader) {
    Assert.notNull(metadataReader, "MetadataReader must not be null");
    this.metadata = metadataReader.getAnnotationMetadata();
    // Get the full path string of the class and set it on the beanClass attribute. Here, the string is set
    setBeanClassName(this.metadata.getClassName());
    setResource(metadataReader.getResource());
}



The corresponding position for resolving this field is:

AbstractBeanFactory class


AbstractAutowireCapableBeanFactory class


AbstractBeanFactory class

  • Judge whether the attribute is a class
  • If it is a class, the value of the field can be used directly
  • If it is not a class, parse the corresponding string and parse it into a class

That's why we can set it directly to class.



To further verify our explanation, I modified my test code:

I set the corresponding class as the path of the class, which can still complete the addition of BeanDefinition


If the test is successful, I also need to make a small change to the Class AbstractBeanDefinition, because the original setBeanClass method can only set the parameter to Class type. I added a method set to string attribute myself





2, BeanDefinitionMap

It is also known from the name that it is a collection storing BeanDefinition. At this time, the Bean is only an embryonic form, which only contains some simple basic information of the Bean

Understand the relationship between singleton pool and Bean by analogy.

With so much knowledge of BeanDefinition in front, you can better understand BeanDefinition map in the back.

private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);



When Spring scans a class that meets the requirements, it will instantiate the class as a BeanDefinition, and then add the BeanDefinition to the BeanDefinitionMap, that is, the following codes (there are many such codes in Spring):

registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());



Call the registerBeanDefinition method. The parameters are: the name of the Bean. Instantiate the implementation class of the BeanDefinition through the class (the specific implementation classes have been described in detail above).

The registry object has three implementations, but in fact, GenericApplicationContext and DefaultListableBeanFactory will eventually call the same method, so there will only be two different implementation classes simplebandefinitionregistry and DefaultListableBeanFactory:

This registry object can be simply understood as a Spring container, which means which container we register the BeanDefinition into (there are many implementations of Spring containers) BeanDefinitionMap.





3, Rootbeandefinition

This is one of many implementations of beandefinition (which will be replaced by BD later)

It is also the destination of many BDS. No matter what type of BD you have in front of you, when instantiating a Bean, you will judge some elements of the Bean. At this time, all BDS will be transformed into RootBD, and then complete the next operation



private final Map<String, RootBeanDefinition> mergedBeanDefinitions = new ConcurrentHashMap<>(256);

The logic of this transformation is to get it from the mergedBeanDefinitions collection (analogically understand BDMap, but the former stores the BD interface and the latter stores the BD subclass RootBD class)

  • If there is in the corresponding mergedBeanDefinitions, it will be returned directly
  • If not, get it from BDMap and convert it to the data in mergedBeanDefinitions


To sum up, when Spring scans the qualified classes, it instantiates the qualified classes into various types of BD (described at the beginning of this article). However, when we instantiate all non lazy loaded beans (complete the Bean life cycle), we will unify all BD types and convert them into RootBD for unified judgment of the corresponding BD information




1. Specific acquisition process

As mentioned above, the conversion of various types of BD to RootBD is to obtain from the corresponding set and then return, but in fact, the process of obtaining is relatively complex. The following sections explain the process separately.

Before looking at the logic of the code, you need some other knowledge



Supplementary knowledge points:

Parent-child container, parent-child Bean

In Spring, we can configure parent-child containers to load beans in two containers, which is used in Spring MVC. Similarly, we can configure parent-child beans. The code is as follows

public class BianChengShiSpringTest2 {
	public static void main(String[] args) {


		AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConfigBean.class);
		AnnotationConfigApplicationContext context2 = new AnnotationConfigApplicationContext(ConfigBean2.class);
		// Building parent-child containers
		context.setParent(context);

		AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
		beanDefinition.setBeanClass(Teacher.class);

		AbstractBeanDefinition beanDefinition2 = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
		beanDefinition2.setBeanClass(MoBian.class);
		// Building parent-child beans
		beanDefinition2.setParentName("teacher");

		context.registerBeanDefinition("teacher",beanDefinition);
		context.registerBeanDefinition("mobian",beanDefinition2);
		System.out.println(context.getBean("teacher"));
		System.out.println(context.getBean("mobian"));
	}
}




The detailed comments obtained have been written in the code

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) {
            // Situation 1
            // Get the object from the mergedBeanDefinitions collection according to the beanName
            mbd = this.mergedBeanDefinitions.get(beanName);
        }

        // No in the collection, enter if (with direct return)
        if (mbd == null) {
            // Judge whether there is a parent BD in BD
            if (bd.getParentName() == null) {
                // Use copy of given root bean definition.
                // When there is no parent Bean (all beans we usually define have no parent Bean), if bd is of RootBD type, clone an attribute
                if (bd instanceof RootBeanDefinition) {
                    // Situation II
                    mbd = ((RootBeanDefinition) bd).cloneBeanDefinition();
                }
                else {
                    // Situation III
                    // If it is not RootBD, we will pass the previous BD attribute to RootBD for data initialization of RootBD
                    mbd = new RootBeanDefinition(bd);
                }
            }
            else {
                // Child bean definition: needs to be merged with parent.
                BeanDefinition pbd;
                try {
                    // Entering this code block indicates that there is a parent Bean
                    
                    // The code here is related to the Bean implementation that implements the BeanFactory interface. In addition, the beanName passed in is equal to the returned beanName
                    // If there is a parent Bean, get the name of the parent Bean
                    String parentBeanName = transformedBeanName(bd.getParentName());
                    if (!beanName.equals(parentBeanName)) {
                        // Situation IV
                        // If the name of the parent Bean is different from itself, this method is called recursively to find the data of the parent Bean
                        pbd = getMergedBeanDefinition(parentBeanName);
                    }
                    else {
                        // If the name of the parent Bean is the same as itself, get the parent Spring container
                        // Only one Bean with the same name can appear in the same Spring container, so when this happens, the parent Bean will only exist in the parent container
                        BeanFactory parent = getParentBeanFactory();
                        if (parent instanceof ConfigurableBeanFactory) {
                            // Situation V
                            // Get BD data information recursively from the parent container
                            pbd = ((ConfigurableBeanFactory) parent).getMergedBeanDefinition(parentBeanName);
                        }
                        // In addition, throw exceptions
                        else {
                            throw new NoSuchBeanDefinitionException(parentBeanName,
                                    "Parent name '" + parentBeanName + "' is equal to bean name '" + beanName +
                                    "': cannot be resolved without a ConfigurableBeanFactory parent");
                        }
                    }
                }
                catch (NoSuchBeanDefinitionException ex) {
                    throw new BeanDefinitionStoreException(bd.getResourceDescription(), beanName,
                            "Could not resolve parent bean definition '" + bd.getParentName() + "'", ex);
                }
                // Deep copy with overridden values.
                // Overwrite the contents of the parent BD to the child BD
                mbd = new RootBeanDefinition(pbd);
                // Overwrite the original data of the sub BD again. To sum up, the parent class is used for attributes not in the child BD, and some attributes use their own original ones
                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);
            }
        }

        return mbd;
    }
}

The core steps are summarized into the following mapping logic for reference:

  • Be sure to understand the concepts of parent-child container and parent-child Bean
  • Bean s with the same name cannot appear in the same container (in essence, the singleton pool of the Spring container is a map. When two identical key s appear in the map, the data will be overwritten, which is naturally a problem)

Posted by bombayduck on Sat, 20 Nov 2021 05:03:48 -0800