Parsing BeanDefinitionRegistry merged with BeanDefinition

 

This article is shared from Huawei Cloud Community " The Road to Spring Master 12-Analysis of the Merger of BeanDefinitionRegistry and BeanDefinition ", author: Zhuanyeyang__.

1. What is BeanDefinitionRegistry?

BeanDefinitionRegistry is a very important interface that exists in Spring's org.springframework.beans.factory.support package, which is the core component for registering and managing BeanDefinition in Spring.

In Spring, a Bean is an object managed by Spring, and a BeanDefinition is a configuration description of a Bean, which describes the data of a Bean. It contains information such as the Bean's class name, whether it is an abstract class, a constructor, and attribute values. This metadata will instruct Spring how to create and initialize beans.

Let's take a look at the role of BeanDefinitionRegistry. The main responsibility of BeanDefinitionRegistry is to register and manage these BeanDefinitions. We can think of it as a registry that stores BeanDefinitions, register new BeanDefinitions to it, or retrieve and delete existing BeanDefinitions. It provides some methods, such as registerBeanDefinition(String, BeanDefinition), removeBeanDefinition(String), and getBeanDefinition(String), for performing these operations.

Inside Spring, BeanDefinitionRegistry is usually implemented by BeanFactory, especially DefaultListableBeanFactory and GenericApplicationContext, which both implement this interface.

2. Why do you need a BeanDefinitionRegistry?

How would some of Spring's core functionality be affected if the BeanDefinitionRegistry didn't exist?

1. The unity of resource analysis : BeanDefinition stores Bean configuration information as a unified data structure. Without BeanDefinitionRegistry, each configuration method (XML, annotations, Java configuration) requires its own dedicated data structure. Not only does this lead to increased code complexity for resource parsing, it can also create inconsistencies between different parsing mechanisms.

2. Dependency lookup and injection : BeanDefinitionRegistry provides a central location, which acts as a central storage for Bean definitions, and can quickly find Bean definitions. Without it, when a bean's dependencies need to be injected, Spring not only needs to traverse all configuration sources to find the corresponding bean, but also may encounter the problem of inconsistent bean definitions, which will significantly reduce performance and accuracy.

Examples of inconsistent Bean definitions are as follows:

<!-- in config1.xml -->

<bean id="sampleBean" class="com.example.SampleBean1" />

<!-- in config2.xml -->

<bean id="sampleBean" class="com.example.SampleBean2" />

Here, sampleBean is defined in both configuration files, but they refer to different classes. If there is no BeanDefinitionRegistry to centralize these definitions, then Spring may encounter confusion when trying to initialize sampleBean. For example, when Spring tries to create an instance of ServiceA and inject sampleBean into it, a question will arise: Which sampleBean definition should Spring choose? com.example.SampleBean1 or com.example.SampleBean2?

This is the so-called "Bean definition inconsistency" problem. If Spring doesn't know which definition is correct, it could inject the wrong bean, causing the application to misbehave or fail. It can also lead to unpredictable errors at runtime because the injected bean is not the version or type expected by the application.

By using a BeanDefinitionRegistry, Spring can detect such problems at application startup and provide clear error messages when bean definitions conflict or are inconsistent, rather than encountering undefined behavior or errors at runtime.

3. Lazy initialization and scope management : The BeanDefinitionRegistry stores the scope and other metadata of the Bean. Without this BeanDefinitionRegistry, Spring needs to re-parse the original configuration resource when performing bean lazy loading or creating beans according to the scope, which increases processing time and may lead to potential configuration errors.

4. Configuration verification : When all BeanDefinitions are registered to BeanDefinitionRegistry, Spring can perform configuration verification, such as checking circular dependencies, ensuring the integrity of Bean definitions, etc. Without BeanDefinitionRegistry, Spring needs to check every time Bean is initialized, which not only leads to performance degradation, but also may miss some obscure configuration problems.

5. Lifecycle management : There is no BeanDefinitionRegistry to store information such as lifecycle callbacks and initialization methods. When Spring manages the lifecycle of Beans, it needs to obtain this information from the original configuration source. This not only increases the complexity of management, but also makes lifecycle callbacks complex and cumbersome.

In short, without BeanDefinitionRegistry, Spring will lose centralized Bean management, resulting in reduced efficiency, scattered error handling, and increased complexity of lifecycle management. BeanDefinitionRegistry ensures the efficient, consistent and stable operation of Spring.

3. Use of BeanDefinitionRegistry

3.1 Simple example of BeanDefinitionRegistry

In this example, we will create a simple bean, register it to DefaultListableBeanFactory (which implements the BeanDefinitionRegistry interface), and then get and use this bean from the factory.

The whole code is as follows:

First, we need a Bean class, which is a simple POJO class:

package com.example.demo.bean;

public class MyBean {

private String message;

public void doSomething() {

System.out.println("Hello, world!");

}

public void setMessage(String message){

this.message = message;

}

public void getMessage(){

System.out.println("Your Message : " + message);

}

}

Then, we can use DefaultListableBeanFactory and RootBeanDefinition to create and register this bean:

package com.example.demo;

import com.example.demo.bean.MyBean;

import org.springframework.beans.factory.config.BeanDefinition;

import org.springframework.beans.factory.support.DefaultListableBeanFactory;

import org.springframework.beans.factory.support.RootBeanDefinition;

public class DemoApplication {

public static void main(String[] args) {

// Create a BeanDefinitionRegistry

DefaultListableBeanFactory registry = new DefaultListableBeanFactory();

// Create a BeanDefinition

BeanDefinition beanDefinition = new RootBeanDefinition(MyBean.class);

// Register BeanDefinition

registry.registerBeanDefinition("myBean", beanDefinition);

// Get Bean from BeanFactory

MyBean myBean = registry.getBean("myBean", MyBean.class);

// use beans

myBean.doSomething(); // output: Hello, world!

}

}

This program creates a Bean called "myBean", which is an instance of the MyBean class. Then, we get the Bean from the BeanFactory and call its doSomething method, which prints "Hello, world!".

cke_142.png

3.2 Examples of implementation classes of ImportBeanDefinitionRegistrar

This is mentioned in the 8th article ( Spring Master's Road 8-The Art of Spring Bean Module Assembly: @Import Detailed Explanation ), which is section 3.5. You can look back, and the code will not be pasted here.

4. Merge of BeanDefinition

When we explained BeanDefinition in the previous article, we did not explain the merger of BeanDefinition. Here is a supplementary explanation.

1.BeanDefinition

In Spring, BeanDefinition is an interface that defines Bean configuration information, such as Bean class name, whether it is a singleton, dependencies, etc. In Spring, each Bean corresponds to a BeanDefinition object.

2. Significance of Merger

In Spring, there is a special BeanDefinition called child BeanDefinition, which is the one we specify through the parent attribute in the XML configuration file. This child BeanDefinition can inherit the configuration information of the parent BeanDefinition.

The process of merging is to combine the configuration information of the child BeanDefinition with the configuration information of the parent BeanDefinition to form a complete configuration information. The merged BeanDefinition object contains all the information needed for Bean creation, and Spring will use this complete BeanDefinition to create Bean instances.

3. The process of merging

When Spring needs to create a Bean instance, it will first obtain the corresponding BeanDefinition object. If this BeanDefinition is a child BeanDefinition, Spring will find its parent BeanDefinition, and then combine the configuration information of the two to form a complete BeanDefinition.

This process is carried out in the getMergedBeanDefinition method of DefaultListableBeanFactory. If you are interested, you can set a breakpoint in this method and take a look at the specific merging process.

4. Flow chart of the merger process

cke_143.png

We can use this feature in the way of parent-child beans. The following is an example of XML configuration:

<bean id="parentBean" class="com.example.ParentClass" abstract="true">

<property name="commonProperty" value="commonValue" />

</bean>

<bean id="childBean" parent="parentBean">

<property name="specificProperty" value="specificValue" />

</bean>

In this example, we define two beans, one is parentBean and the other is childBean. parentBean is abstract, which means it will not be instantiated, but only used as a template. The parent property of childBean points to parentBean, which means it inherits the configuration of parentBean.

parentBean has an attribute commonProperty with a value of commonValue. childBean has an attribute specificProperty with a value of specificValue. When Spring parses this configuration file and generates BeanDefinition, the BeanDefinition of childBean will contain two attributes: commonProperty and specificProperty, which is the merging process of BeanDefinition.

In Java configuration, we cannot directly simulate the BeanDefinition merging process of XML configuration , because this is a feature of Spring XML configuration. Configuration classes usually use Java code inheritance or combination to reuse bean definitions, and no configuration metadata is involved. Levels of BeanDefinition are merged. The BeanDefinition merge feature in the XML configuration allows us to define a parent Bean, and then define some child beans, which can inherit some properties of the parent Bean.

This feature does not have a direct replacement in Java configuration, because Java configuration usually relies more on the logic of the instantiation process than metadata (ie BeanDefinition). In Java configuration, we can use normal Java features like inheritance and composition to achieve similar results, but this is not true BeanDefinition merging. Therefore, when we convert from XML configuration to Java configuration, we usually need to manually copy the shared properties into the definition of each bean.

4.1 Debugging and verifying the merger of BeanDefinition

The whole code is as follows:

First, create the XML configuration file applicationContext.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"

xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

<bean id="parentBean" class="com.example.demo.bean.ParentClass" abstract="true">

<property name="commonProperty" value="commonValue" />

</bean>

<bean id="childBean" parent="parentBean" class="com.example.demo.bean.ChildClass">

<property name="specificProperty" value="specificValue" />

</bean>

</beans>

Then, create ParentClass and ChildClass as follows:

package com.example.demo.bean;

public abstract class ParentClass {

private String commonProperty;

public String getCommonProperty() {

return commonProperty;

}

public void setCommonProperty(String commonProperty) {

this.commonProperty = commonProperty;

}

}

package com.example.demo.bean;

public class ChildClass extends ParentClass {

private String specificProperty;

public String getSpecificProperty() {

return specificProperty;

}

public void setSpecificProperty(String specificProperty) {

this.specificProperty = specificProperty;

}

}

The main program is as follows:

package com.example.demo;

import org.springframework.beans.factory.config.BeanDefinition;

import org.springframework.beans.factory.support.DefaultListableBeanFactory;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class DemoApplication {

public static void main(String[] args) {

ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

DefaultListableBeanFactory factory = (DefaultListableBeanFactory) context.getBeanFactory();

// Get the original BeanDefinition of childBean

BeanDefinition childBeanDefinition = factory.getBeanDefinition("childBean");

System.out.println("Child bean definition before merge: " + childBeanDefinition);

// Get the merged BeanDefinition

BeanDefinition mergedBeanDefinition = factory.getMergedBeanDefinition("childBean");

System.out.println("Merged bean definition: " + mergedBeanDefinition);

}

}

In this example, we first load the applicationContext.xml configuration file and then get the original BeanDefinition of the childBean. Then, we call the getMergedBeanDefinition method to obtain the merged BeanDefinition. You can set breakpoints during this process to view the details of the merge process.

operation result:

cke_144.png

You can see from the running results that Generic bean and Root bean are printed, representing GenericBeanDefinition and RootBeanDefinition.

Why are there two different BeanDefinition types (GenericBeanDefinition and RootBeanDefinition) ?

GenericBeanDefinition:

  • This is a generic BeanDefinition implementation class that can configure any type of bean.
  • It is typically used to read XML, annotations or other forms of configuration.
  • Compared with other specific BeanDefinitions, it is relatively simple and lightweight.
  • When a bean is defined in XML using the <bean> element, a GenericBeanDefinition instance is usually created for the bean.

RootBeanDefinition:

  • This is a complete bean definition, including all configuration information of the bean, such as constructor parameters, property values, method overrides, etc.
  • It is usually used to merge parent and child bean definitions. That is, when a bean definition inherits from another bean definition, the RootBeanDefinition is responsible for holding the resulting merged configuration.
  • In addition to GenericBeanDefinition, it also contains many internal details related to bean instantiation, dependency resolution and initialization.
  • In Spring's internal workflow, although there can be various BeanDefinition implementations at the beginning, they are usually converted to RootBeanDefinition in the post-processing stage of the container, because a complete and fixed bean definition is required for bean creation at this stage .

Debugging point 1 : We get the original BeanDefinition of the child bean from the BeanFactory. This BeanDefinition only represents the metadata configured for the child bean in XML, without merging with the parent Bean, only the specificProperty attribute can be seen.

cke_145.png

Debugging point 2 : After using getMergedBeanDefinition, the type of BeanDefinition printed on the console becomes RootBeanDefinition. At this time, we obtain the BeanDefinition of the merged child Bean from BeanFactory. Since the BeanDefinition of the child Bean has been merged with the BeanDefinition of the parent Bean, a complete property set can be seen. Here, two property key-value pairs are seen in propertyValues: commonProperty and specificProperty, which indicates that the child Bean inherits the properties of the parent Bean value.

cke_146.png

Note that the purpose of this example is to demonstrate the BeanDefinition's merging process, so we directly manipulate the BeanFactory. In actual application development, we generally do not directly operate BeanFactory.

4.2 The purpose of BeanDefinition merger

  1. Provide complete BeanDefinition information : In configuration, we often use parent-child BeanDefinition (such as through the parent attribute of the <bean> tag). The child BeanDefinition may only define the bean properties that need to be changed or increased, while the parent BeanDefinition provides shared default definitions. In this case, the merge operation will merge the parent and child BeanDefinition information into a complete BeanDefinition for subsequent bean creation.
  2. Optimized performance : The result of the merge operation is usually cached, so the merged BeanDefinition can be directly obtained from the cache when the same bean is obtained next time, avoiding repeated merge operations, thereby improving performance.
  3. Resolving circular dependencies : When dealing with circular dependencies between beans, it is necessary to throw out the beans that have been processed (such as instantiation and property filling) as early as possible, and then a complete BeanDefinition information is required. Therefore, the merging of BeanDefinition also plays an important role in solving the circular dependency problem.

In short, the merging of BeanDefinition is to obtain a complete and accurate BeanDefinition for the subsequent bean creation and dependency resolution of the Spring IoC container.

4.3 Graphical BeanDefinition Merger and Spring Initialization Relationship

cke_147.png

1. Resource positioning

At this stage, Spring will determine the location of the resources to be loaded according to the user's configuration. The resources may come from various configuration methods, such as XML, Java annotations or Java configuration.

2. Read configuration

Spring reads Bean definition information from a determined configuration source

  • For XML configuration, the parser processes each <bean> element. At this time, in particular, definitions of parent-child Bean relationships exist, which are resolved to the original BeanDefinition, but not merged.
  • For annotations and Java configuration, BeanDefinition is parsed as an independent definition, usually no parent-child relationship is involved.

3. Register BeanDefinition

Spring will register all parsed BeanDefinitions in the BeanDefinitionRegistry.

4. Handle BeanDefinition

At this stage, Spring performs BeanDefinition preprocessing.

  • If there is a parent-child relationship between the beans read from the XML configuration, they will be merged at this time. The merged BeanDefinition ensures that the child Bean inherits all the properties of the parent Bean and can override them.
  • For bean definitions based on annotations or Java configuration, this kind of merging operation usually does not happen because there is no clear parent-child relationship.

5. Bean instantiation and property filling

  • This phase marks the beginning of the Spring lifecycle.
  • All BeanDefinition, whether original or merged, will be converted into actual Bean instances at this stage.
  • The Spring container will be responsible for managing the complete life cycle of these beans, including but not limited to dependency injection and property setting.

6. Bean initialization

  • Including calling the initialization method of the Bean, such as implementing the afterPropertiesSet method of the InitializingBean interface or the custom initialization method specified by the init-method attribute.
  • At this stage, the bean is fully ready to be used by the application.

7. Register the destruction method of the Bean

  • Spring will track and register the bean's destruction method.
  • This ensures that when the Spring container shuts down, it will correctly call each bean's destroy method, such as the destroy method that implements the DisposableBean interface or a custom method specified through the destroy-method attribute.

From here you can see that the merging of the BeanDefinition occurs at an early stage before the actual Bean instantiation, which ensures that when Spring goes to create a Bean instance, it has a complete, merged definition to rely on.

5. Source code analysis of BeanDefinition merge

5.1 Sequence Diagram of BeanDefinition Merging Process

cke_148.png

5.2 Interpretation of source code of BeanDefinition merge process

Let’s talk about the merger of BeanDefinition that was not mentioned in the previous article. Let’s analyze the source code of Spring 5.3.7, show the diagram first, and then analyze it later.

cke_149.png

Let's analyze several methods of the AbstractBeanFactory class.

// Get the locally merged BeanDefinition

protected RootBeanDefinition getMergedLocalBeanDefinition(String beanName) throws BeansException {

// Get the merged BeanDefinition from the cache

RootBeanDefinition mbd = (RootBeanDefinition)this.mergedBeanDefinitions.get(beanName);

// If the BeanDefinition in the cache is not empty and not expired, return directly

// Otherwise, merge the BeanDefinition

return mbd != null && !mbd.stale ? mbd : this.getMergedBeanDefinition(beanName, this.getBeanDefinition(beanName));

}

// Get the merged BeanDefinition

protected RootBeanDefinition getMergedBeanDefinition(String beanName, BeanDefinition bd) throws BeanDefinitionStoreException {

// Directly call the getMergedBeanDefinition method and set containingBd to null

return this.getMergedBeanDefinition(beanName, bd, (BeanDefinition)null);

}

// Get the merged BeanDefinition

protected RootBeanDefinition getMergedBeanDefinition(String beanName, BeanDefinition bd, @Nullable BeanDefinition containingBd) throws BeanDefinitionStoreException {

synchronized(this.mergedBeanDefinitions) {

RootBeanDefinition mbd = null;

RootBeanDefinition previous = null;

// If there is no included BeanDefinition, then get the merged BeanDefinition from the cache

if (containingBd == null) {

mbd = (RootBeanDefinition)this.mergedBeanDefinitions.get(beanName);

}

// If the BeanDefinition in the cache is empty or outdated, create a new BeanDefinition for merging

if (mbd == null || mbd.stale) {

previous = mbd;

// If bd has no parent name, that is, it does not inherit other beans

// Then directly clone this bd to generate a RootBeanDefinition

if (bd.getParentName() == null) {

if (bd instanceof RootBeanDefinition) {

mbd = ((RootBeanDefinition)bd).cloneBeanDefinition();

} else {

mbd = new RootBeanDefinition(bd);

}

} else { // If bd is a child BeanDefinition (that is, has a parent BeanDefinition)

// First get the parent BeanDefinition

BeanDefinition pbd;

try {

String parentBeanName = this.transformedBeanName(bd.getParentName());

if (!beanName.equals(parentBeanName)) {

pbd = this.getMergedBeanDefinition(parentBeanName);

} else {

BeanFactory parent = this.getParentBeanFactory();

if (!(parent instanceof ConfigurableBeanFactory)) {

throw new NoSuchBeanDefinitionException(parentBeanName, "Parent name '" + parentBeanName + "' is equal to bean name '" + beanName + "': cannot be resolved without a ConfigurableBeanFactory parent");

}

pbd = ((ConfigurableBeanFactory)parent).getMergedBeanDefinition(parentBeanName);

}

} catch (NoSuchBeanDefinitionException var11) {

throw new BeanDefinitionStoreException(bd.getResourceDescription(), beanName, "Could not resolve parent bean definition '" + bd.getParentName() + "'", var11);

}

// Create a new RootBeanDefinition and override the properties in bd

// This completes the merging of parent and child BeanDefinitions

mbd = new RootBeanDefinition(pbd);

mbd.overrideFrom(bd);

}

// If the merged BeanDefinition does not specify a scope

// The default setting is singleton

if (!StringUtils.hasLength(mbd.getScope())) {

mbd.setScope("singleton");

}

// If the parent BeanDefinition is defined and the scope of the parent BeanDefinition is not singleton but the scope of the child BeanDefinition is singleton

// Set the scope of the child BeanDefinition to the scope of the parent BeanDefinition

if (containingBd != null && !containingBd .isSingleton() && mbd.isSingleton()) {

mbd.setScope(containingBd.getScope());

}

// If there is no included BeanDefinition and the BeanMetadata needs to be cached

// Then put this newly created and merged BeanDefinition into the mergedBeanDefinitions cache

if (containingBd == null && this.isCacheBeanMetadata()) {

this.mergedBeanDefinitions.put(beanName, mbd);

}

}

// If there was an expired BeanDefinition before

// Then copy the relevant cache from the expired BeanDefinition to the new BeanDefinition

if (previous != null) {

this.copyRelevantMergedBeanDefinitionCaches(previous, mbd);

}

// Return the merged BeanDefinition

return mbd;

}

}

This code mainly completes the merge work of BeanDefinition. When a BeanDefinition has a parent BeanDefinition, Spring will merge the definition of the child BeanDefinition with the definition of the parent BeanDefinition to generate a new complete BeanDefinition. This process is the merging of BeanDefinitions. The merged BeanDefinition will be cached for next use. If a BeanDefinition has no parent BeanDefinition, then directly clone a copy as the merged BeanDefinition. During the entire life cycle of Spring, the merging of BeanDefinition may occur multiple times, and every time a Bean is obtained, the merging of BeanDefinition will be performed first.

Click to follow and learn about Huawei Cloud's fresh technologies for the first time~

Redis 7.2.0 was released, the most far-reaching version Chinese programmers refused to write gambling programs, 14 teeth were pulled out, and 88% of the whole body was damaged. Flutter 3.13 was released. System Initiative announced that all its software would be open source. The first large-scale independent App appeared , Grace changed its name to "Doubao" Spring 6.1 is compatible with virtual threads and JDK 21 Linux tablet StarLite 5: default Ubuntu, 12.5-inch Chrome 116 officially released Red Hat redeployed desktop Linux development, the main developer was transferred away Kubernetes 1.28 officially released
{{o.name}}
{{m.name}}

Guess you like

Origin my.oschina.net/u/4526289/blog/10100466