BeanDefinition decryption: the cornerstone of building and managing Spring Beans

This article is shared from Huawei Cloud Community " The Road to Spring Master 11 - BeanDefinition Decryption: The Cornerstone of Building and Managing Spring Beans ", author: Zhuanyeyang__.

BeanDefinition is a very important concept in Spring, which contains all the information needed by the Spring container to create and configure beans. Understanding BeanDefinition can help us gain a deep understanding of Spring's inner workings.

1. Exploring BeanDefinitions

First, let us have an overall understanding of BeanDefinition.

1.1 Interpretation of BeanDefinition in official documents

Spring's official documentation is a very important resource for understanding the concepts and components of the Spring framework. Regarding BeanDefinition, the official documentation is as follows:

BeanDefinition contains a lot of configuration information, which can guide Spring how to create beans, including Bean constructor parameters, property values, initialization methods, static factory method names, and so on. In addition, the child BeanDefinition can also inherit configuration information from the parent BeanDefinition, and can also overwrite or add new configuration information. This design pattern effectively reduces redundant configuration information and makes the configuration more concise.

Next, let us better understand BeanDefinition through a concrete example.

Consider a simple Java class, Person:

public class Person {

private String name;

private int age;

public Person() {}

public Person(String name, int age) {

this.name = name;

this.age = age;

}

// getters and setters

}

We can use XML configuration or Java configuration to define a Bean of type Person, and the configuration information of this Bean will be encapsulated in BeanDefinition.

In XML configuration, a Person Bean might be defined as follows:

<bean id="person" class="com.example.Person">

<constructor-arg name="name" value="John"/>

<constructor-arg name="age" value="25"/>

</bean>

Here, the BeanDefinition information includes the class attribute (fully qualified class name) and the name and value of the constructor parameter.

In the Java configuration, we can define a Person Bean like this:

@Configuration

public class AppConfig {

@Bean

public Person person() {

return new Person("John", 25);

}

}

In this example, the BeanDefinition's information includes the class attribute (fully qualified class name) and constructor parameters. We can get this fully qualified class name through the getBeanClassName() method of BeanDefinition.

1.2 Analysis of key methods of BeanDefinition

The BeanDefinition interface defines all the meta information of the Bean, mainly including the following methods:

  • get/setBeanClassName()  - Gets/sets the Bean's class name
  • get/setScope()  - get/set the scope of the bean
  • isSingleton() / isPrototype()  - Determine if singleton/prototype scope
  • get/setInitMethodName()  - get/set initialization method name
  • get/setDestroyMethodName()  - get/set destroy method name
  • get/setLazyInit()  - get/set whether to lazy initialize
  • get/setDependsOn()  - get/set dependent Bean
  • get/setPropertyValues()  - get/set property values
  • get/setAutowireCandidate()  - get/set whether autowire is possible
  • get/setPrimary()  - get/set whether the preferred autowired bean

Since the source code of BeanDefinition is quite long, we will not post all of them here, and you can check them yourself. BeanDefinition also implements the AttributeAccessor interface, through which custom metadata can be added. The following sections will give examples of the use of AttributeAccessor.

As can be seen from the above, BeanDefinition is a metadata object used to describe Bean in the Spring framework. This metadata contains some basic information about Bean, including the following aspects:

  • Bean class information : This is the fully qualified class name of the Bean, that is, the specific type of the Bean after instantiation.
  • Bean attribute information : including the scope of the bean (whether it is a singleton or a prototype), whether it is the main bean (primary), description information, etc.
  • Bean behavior characteristics : For example, whether the Bean supports lazy loading, whether it can be used as a candidate for automatic assembly, and the initialization and destruction methods of the Bean, etc.
  • The relationship between Bean and other Beans : For example, other Beans that this Bean depends on, and whether this Bean has a parent Bean.
  • Bean configuration information : This includes the Bean's constructor parameters, property values, etc.

1.3 The practical application of some methods of BeanDefinition

Next, use a detailed code example to illustrate the use of each method in the BeanDefinition interface, and combine the actual code example to illustrate the actual meaning of these methods. Below, I will provide code examples for several important aspects of BeanDefinition.

The whole code is as follows:

First, here's our Java configuration class and the definition of the Person class:

package com.example.demo.configuration;

import com.example.demo.bean.Person;

import org.springframework.context.annotation.*;

@Configuration

public class AppConfig {

@Bean(initMethod = "init", destroyMethod = "cleanup")

@Scope("singleton")

@Lazy

@Primary

@Description("A bean for person")

public Person person() {

return new Person("John", 25);

}

}

package com.example.demo.bean;

public class Person {

private String name;

private int age;

public Person(String name, int age) {

this.name = name;

this.age = age;

}

// getters and setters

public void init() {

System.out.println("Initializing Person bean");

}

public void cleanup() {

System.out.println("Cleaning up Person bean");

}

}

Here's how to get each attribute through BeanDefinition:

package com.example.demo;

import com.example.demo.configuration.AppConfig;

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

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import java.util.Arrays;

public class DemoApplication {

public static void main(String[] args) {

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

String personBeanName = "person";

BeanDefinition personBeanDefinition = context.getBeanFactory().getBeanDefinition(personBeanName);

// 获取Bean的类信息

System.out.println("Bean Class Name: " + context.getBean(personBeanName).getClass().getName());

// 获取Bean的属性

System.out.println("Scope: " + personBeanDefinition.getScope());

System.out.println("Is primary: " + personBeanDefinition.isPrimary());

System.out.println("Description: " + personBeanDefinition.getDescription());

// 获取Bean的行为特征

System.out.println("Is lazy init: " + personBeanDefinition.isLazyInit());

System.out.println("Init method: " + personBeanDefinition.getInitMethodName());

System.out.println("Destroy method: " + personBeanDefinition.getDestroyMethodName());

// 获取Bean的关系

System.out.println("Parent bean name: " + personBeanDefinition.getParentName());

System.out.println("Depends on: " + Arrays.toString(personBeanDefinition.getDependsOn()));

// 获取Bean的配置属性

System.out.println("Constructor argument values: " + personBeanDefinition.getConstructorArgumentValues());

System.out.println("Property values: " + personBeanDefinition.getPropertyValues());

}

}

operation result:

cke_123.png

This example includes most of the BeanDefinition's methods and shows them in action. Please note that in this example, some methods such as getDependsOn(), getParentName(), getConstructorArgumentValues(), getPropertyValues() return results may not show any real content, because our person bean did not set these values. If these values ​​are set in the actual application, then these methods will return the corresponding results.

1.4 BeanDefinition in-depth information structure combing

In Spring, BeanDefinition contains the following main information:

  • Class : This is the fully qualified class name, and Spring uses this information to create the Bean instance through reflection. For example, com.example.demo.bean.Book, when Spring needs to create an instance of the Book bean, it will create an instance of the Book class through reflection based on this class name.
  • Name : This is the name of the bean. In the application, we usually use this name to get the instance of the bean. For example, we may have a Bean named "bookService", and we can get an instance of this Bean through context.getBean("bookService").
  • Scope : This defines the scope of the bean, such as singleton or prototype. If scope is singleton, then the Spring container will only create one instance of the bean and return this instance on each request. If scope is prototype, the Spring container will create a new bean instance each time the bean is requested.
  • Constructor arguments : These are the constructor arguments used to instantiate the bean. For example, if we have a Book class whose constructor requires a parameter title of type String, then we can set the constructor arguments in the BeanDefinition to provide this parameter.
  • Properties : These are the property values ​​that need to be injected into the bean. For example, we might have a Book class that has a title property, and we can set properties in the BeanDefinition to provide the value of this property. These values ​​can also be injected in configuration files or classes via <property> tags or @Value annotations.
  • Autowiring Mode : This is the autowiring mode. If set to byType, the Spring container will autowire the properties of the bean, it will find the bean in the container that matches the property type and inject it. If set to byName, the container will look for a bean in the container whose name matches the property name and inject it. Another option is constructor, which refers to autowiring dependencies through the parameter types of the Bean constructor.
  • Lazy Initialization : If set to true, the bean will be created on first request rather than on application startup. This can improve application startup speed, but may introduce some delay when the bean is first requested.
  • Initialization Method and Destroy Method : These are the initialization and destruction methods of the bean. For example, we may have a BookService class, which has an initialization method called init and a destruction method called cleanup, we can set these two methods in the BeanDefinition, then the Spring container will call the init method after creating the bean, Instead, the cleanup method is called before the bean is destroyed.
  • Dependency beans : These are the dependencies of the Bean. For example, we may have a BookService Bean that depends on a BookRepository Bean, then we can set the dependency beans to "bookRepository" in the BeanDefinition of BookService, then the Spring container will first create the BookRepository Bean before creating the BookService Bean.

The above is the main information contained in BeanDefinition, which will tell the Spring container how to create and configure beans. Different BeanDefinition implementations may have more configuration information. For example, RootBeanDefinition, ChildBeanDefinition, GenericBeanDefinition, etc. are all concrete implementation classes of the BeanDefinition interface, and they may contain more configuration options.

2. Analysis of BeanDefinition structure system

Let us first clarify the role of BeanDefinition. BeanDefinition is the core component in Spring, which defines the bean configuration information, including class name, scope, constructor parameters, attribute values, etc. Let's take a look at how BeanDefinition is designed in Spring.

Through IDEA, we can get the following inheritance diagram:

cke_124.png

Although there are many interfaces, abstract classes and extensions, we only need to focus on the key parts of them.

2.1 Types of BeanDefinition and their applications

In Spring, the configuration information of a bean is saved by the BeanDefinition object. According to different sources and methods of bean configuration, BeanDefinition is divided into many types, we select a few of them to explain

  • RootBeanDefinition : When we define a bean in the XML configuration file, Spring will create a RootBeanDefinition object for the bean, which contains all the information used to create the bean, such as the bean's class name, attribute values, etc. For example:
<bean id="exampleBean" class="com.example.ExampleBean">

<property name="stringProperty" value="stringValue"/>

</bean>

This XML configuration defines a bean named "exampleBean", whose class is "com.example.ExampleBean", and has a property named "stringProperty" whose value is "stringValue". When Spring reads this configuration, it will create a RootBeanDefinition object to save all configuration information of this bean.

Summary: When defining a bean in an XML file, Spring will create a RootBeanDefinition instance, which will save all configuration information, such as class name, attribute value, etc.

  • ChildBeanDefinition : ChildBeanDefinition can be used when we need to let a bean inherit the configuration of another bean. For example:
<bean id="parentBean" class="com.example.ParentBean">

<property name="stringProperty" value="stringValue"/>

</bean>

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

<property name="anotherStringProperty" value="anotherStringValue"/>

</bean>

In this XML configuration, "childBean" inherits all configurations of "parentBean", and also adds a new attribute "anotherStringProperty". When Spring reads this configuration, it will first create a RootBeanDefinition object for "parentBean", and then create a ChildBeanDefinition object for "childBean", which will refer to the BeanDefinition of "parentBean".

Summary: If you have a bean and want to create a new bean, the new bean needs to inherit all the configuration of the original bean, but also add or modify some configuration information, Spring will create a ChildBeanDefinition instance.

  • GenericBeanDefinition : This is a generic BeanDefinition that can be converted to RootBeanDefinition or ChildBeanDefinition as needed. For example, a bean is defined using the @Bean annotation in a configuration class:
@Configuration

public class AppConfig {

@Bean

public MyComponent myComponent() {

return new MyComponent();

}

}

In this code, we define a bean called "myComponent" whose class is "MyComponent". When Spring parses this configuration class, it creates a GenericBeanDefinition object for the myComponent() method. This GenericBeanDefinition object will hold the name of the method (which is also the name of the bean), the return type, and any required constructor parameters or properties. In this example, we didn't define any parameters or properties, so the GenericBeanDefinition object only contains basic information. This GenericBeanDefinition object can then be used by the Spring container to generate bean instances.

Summary: When a bean is defined using the @Bean annotation in a Java configuration class, Spring will create a GenericBeanDefinition instance.

  • AnnotatedBeanDefinition : When we use annotations (such as @Component, @Service, @Repository, etc.) in the code to define beans, Spring will create an instance of the AnnotatedBeanDefinition interface. For example:
@Component("myComponent")

public class MyComponent {

// some fields and methods

}

In this code, we define a bean named "myComponent", its class is "MyComponent", and there is a @Component annotation on this class. When Spring parses this class, it creates an AnnotatedBeanDefinition object. This AnnotatedBeanDefinition object will save the class name (which is also the name of the bean), the type of the class, and all annotation information on the class. In this example, the AnnotatedBeanDefinition instance will contain the @Component annotation and all its metadata. This AnnotatedBeanDefinition instance can then be used by the Spring container to generate a bean instance, and Spring can also use the annotation information stored in the AnnotatedBeanDefinition for further processing, such as AOP proxy, transaction management, etc.

Summary: When using annotations on a class (such as @Component, @Service, @Repository, etc.) to define a bean, Spring will create an instance that implements the AnnotatedBeanDefinition interface, such as AnnotatedGenericBeanDefinition or ScannedGenericBeanDefinition. This instance will save the class name, class type, and all annotation information on the class.

The main difference between GenericBeanDefinition and AnnotatedBeanDefinition is that AnnotatedBeanDefinition saves the annotation information on the class, while GenericBeanDefinition does not. This enables Spring to read and process these annotations at runtime, providing richer functionality.

In most cases, we don't need to care which BeanDefinition Spring creates for the bean. Spring automatically manages these BeanDefinitions and creates and configures beans based on their types and the information they contain.

2.2 Analysis of the principle of generating BeanDefinition

This BeanDefinition object is read and generated by various BeanDefinitionReader implementation classes during the Spring startup process.

There are three main ways to create a BeanDefinition in Spring:

  • XML configuration method :

First, we define a bean in the XML file:

<bean id="bookService" class="com.example.demo.service.BookService">

<property name="bookRepository" ref="bookRepository"/>

</bean>

<bean id="bookRepository" class="com.example.demo.repository.BookRepository"/>

In this case, when Spring starts, XmlBeanDefinitionReader will read this XML file, parse the <bean> elements in it, and create a BeanDefinition object for each <bean> element.

A brief description is: read the XML file by XmlBeanDefinitionReader, parse the <bean> element and generate a BeanDefinition.

  • Annotation configuration method :

We use @Component, @Service, @Repository and other annotations on the class to define beans, for example:

@Repository

public class BookRepository {

// ... repository methods

}

@Service

public class BookService {

private final BookRepository bookRepository;



public BookService(BookRepository bookRepository) {

this.bookRepository = bookRepository;

}



// ... service methods

}

In this case, when Spring starts, ClassPathBeanDefinitionScanner scans the specified package path, finds all classes with specific annotations, and creates BeanDefinition objects for these classes. The BeanDefinition generated in this way is usually of ScannedGenericBeanDefinition type.

The simple description is: ClassPathBeanDefinitionScanner scans the annotated classes under the specified package path and generates BeanDefinition.

  • Java configuration method :

We use @Configuration and @Bean annotations to define configuration classes and beans, for example:

@Configuration

public class AppConfig {

@Bean

public BookRepository bookRepository() {

return new BookRepository();

}

@Bean

public BookService bookService(BookRepository bookRepository) {

return new BookService(bookRepository);

}

}

In this case, when Spring starts, ConfigurationClassPostProcessor will process these configuration classes and hand them over to ConfigurationClassParser for parsing. A BeanDefinition object is created for each @Bean-marked method in the configuration class. The BeanDefinition generated in this way is usually of type ConfigurationClassBeanDefinition.

The brief description is: ConfigurationClassPostProcessor processes the class marked with @Configuration, parses the @Bean method in it and generates BeanDefinition.

In general, whether we choose XML configuration, annotation configuration or Java configuration, Spring will parse these configurations when it starts, and generate corresponding BeanDefinition objects to guide the Spring container on how to create and manage Bean instances.

These contents may be abstract and complicated, but for beginners, you only need to understand: BeanDefinition is the object used by Spring to store Bean configuration information. It is generated by BeanDefinitionReader reading configuration during Spring startup. The specific generation method Depending on the configuration method used (XML, annotations or Java configuration), as for the specific implementation principle, we will learn more about it later.

2.3 AttributeAccessor in action: a powerful tool for attribute manipulation

AttributeAccessor is an important interface in the Spring framework, which provides a flexible way to attach additional metadata to Spring's core components. In Spring, many important classes, including BeanDefinition, implement the AttributeAccessor interface, so that additional attributes of these components can be dynamically added and obtained. A significant advantage of this is that developers can flexibly manage the extra information of these components without changing the original class definition.

Let's look at an example, the full code is as follows:

First create a Book object

class Book {

private String title;

private String author;

public Book() {}

public Book(String title, String author) {

this.title = title;

this.author = author;

}

// getter 和 setter 省略...

}

Main program:

package com.example.demo;

import com.example.demo.bean.Book;

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

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

public class DemoApplication {

public static void main(String[] args) {

// 创建一个BeanDefinition, BeanDefinition是AttributeAccessor的子接口

BeanDefinition bd = new RootBeanDefinition(Book.class);

// 设置属性

bd.setAttribute("bookAttr", "a value");

// 检查和获取属性

if(bd.hasAttribute("bookAttr")) {

System.out.println("bookAttr: " + bd.getAttribute("bookAttr"));

// 移除属性

bd.removeAttribute("bookAttr");

System.out.println("bookAttr: " + bd.getAttribute("bookAttr"));

}

}

}

In this example, we create a RootBeanDefinition instance to describe how to create an instance of the Book class. RootBeanDefinition is the implementation of BeanDefinition, and BeanDefinition implements the AttributeAccessor interface, so RootBeanDefinition also inherits the method of AttributeAccessor.

Some people may wonder, Book does not have the member variable bookAttr, how is this assigned?

In the Spring framework, the methods defined by the AttributeAccessor interface are for attaching, obtaining, and removing metadata associated with an object (such as RootBeanDefinition), rather than operating the fields of the object (such as Book) itself.

Therefore, when calling the setAttribute("bookAttr", "a value") method on the RootBeanDefinition instance, it is not actually setting a field named bookAttr on the Book instance. Instead, a metadata is attached to the RootBeanDefinition instance. The key of the metadata is "bookAttr" and the value is "a value".

When the getAttribute("bookAttr") method is used subsequently, it will return the previously set metadata value "a value" instead of trying to access the bookAttr field of the Book class (in fact, the Book class does not have a bookAttr field).

Simply put, these metadata are attached to the RootBeanDefinition object, not the Book instance described by the RootBeanDefinition object.

operation result:

cke_125.png

Summarize:

BeanDefinition is an important class that implements the AttributeAccessor interface. The BeanDefinition object is the data structure used by the Spring framework to store bean configuration information. When we define a bean using annotations such as @Bean, @Scope, @Lazy, etc. in the configuration class, Spring will create a BeanDefinition object for the bean and attach the metadata of these annotations to the BeanDefinition object.

When the Spring container needs to create a bean instance later, it will look at the BeanDefinition object, and create and manage the bean instance according to the metadata (such as scope, lazy initialization, initialization and destruction methods, etc.). These metadata are not directly attached to the bean instance, but are stored in the BeanDefinition object, which is managed and used by the Spring container.

So, when we get the BeanDefinition from the ApplicationContext in the main method and print its properties, we are actually looking at the internal data structures that the Spring framework uses to manage the bean, not directly at the state of the bean instance itself.

The advantage of this method is that it separates these additional metadata from the bean instance itself, so that these metadata can be changed flexibly without modifying the bean class, and the AttributeAccessor can be used in different threads in the same JVM process share data between. This is why we can change the scope of the bean, whether it is lazy loading, etc., by modifying the configuration file or annotations, without modifying the class definition of the bean.

3. BeanDefinition review and summary

In the process of our in-depth exploration of the Spring framework, we have learned that BeanDefinition is a very key concept in Spring. The main responsibility of BeanDefinition is as a data object that stores detailed information about how to create, initialize, and configure a specific Bean instance.

In particular, the following main information is contained in the BeanDefinition:

  • Fully qualified class name for the Spring container to create bean instances via reflection.
  • The name and alias of the bean , used to reference and find the bean in the application.
  • The scope of the bean , such as singleton or prototype, determines how Spring manages the life cycle of the bean instance.
  • Constructor parameters and property values ​​for instantiating beans and dependency injection.
  • Autowiring mode , instructs Spring how to automatically inject dependencies.
  • Initialization and destruction methods that let Spring know how to execute custom logic at specific moments in the bean's lifecycle.
  • Bean dependencies tell Spring which beans need to be created before creating the current bean.

No matter which configuration method we use (XML, annotations or Java configuration), Spring will parse these configurations at startup and generate corresponding BeanDefinition objects. These BeanDefinition objects are like recipes inside the Spring container, telling the Spring container how to create and configure each Bean.

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

Guess you like

Origin blog.csdn.net/devcloud/article/details/132225149