XiaoLin's Blog

Xiao Lin

Spring 中的 BeanDefinition

36
2024-02-04

在Spring中,BeanDefinition是一个接口,用于描述Spring容器中的Bean的配置元数据。它定义了Bean的名称、类型、作用域以及其他配置信息。

BeanDefinition可以通过XML配置文件、Java注解或者Java代码等方式来定义。它包含以下重要属性:

  1. Bean的名称:每个BeanDefinition都有一个唯一的名称,用于在容器中标识和获取对应的Bean实例。

  2. Bean的类型:指定了该Bean实例所对应的类或接口。

  3. Bean的作用域:描述了该Bean实例在容器中的生命周期和可见范围。常见的作用域包括单例(Singleton)、原型(Prototype)、请求(Request)、会话(Session)等。

  4. Bean之间的依赖关系:描述了该Bean与其他Bean之间的依赖关系,例如通过构造函数、属性注入等方式。

  5. Bean的初始化方法和销毁方法:描述了在容器创建和销毁Bean实例时需要执行的方法。

  6. 其他配置信息:如是否延迟初始化、是否懒加载、是否可自动装配等。

通过解析和读取配置文件或注解,Spring可以根据这些BeanDefinition来创建对应的Bean实例,并将其管理起来。这使得Spring可以基于不同需求灵活地创建和管理各种类型的对象,并且可以提供自动装配、AOP等功能。

BeanDefinition 的层次关系

Pasted image 20230711085523.png

BeanDefinition 是一个接口,提供了设置 Bean 初始化信息的一些方法,如设置作用于、设置依赖关系等

AbstractBeanDefinition 是一个继承了BeanDefinition的抽象类,他对BeanDefinition中需要设置的属性进行了具体的实现。

	//  Class对象
	@Nullable
	private volatile Object beanClass;
	// 作用域,默认为单例
	@Nullable
	private String scope = SCOPE_DEFAULT;
	// 成员变量
	@Nullable
	private MutablePropertyValues propertyValues;
	// 有参构造方法
	@Nullable
	private ConstructorArgumentValues constructorArgumentValues;
	// 依赖注入的 bean	
	@Nullable
	private String[] dependsOn;

ConstructorArgumentValues

ConstructorArgumentValues类用于保存构造函数参数值的信息。它是一个用于存储和访问构造函数参数值的容器类。

ConstructorArgumentValues类的主要作用是向容器注册构造函数参数值,并在需要时提供对这些参数值的访问。它允许用户通过指定参数索引或参数名称来获取对应的参数值。

具体而言,ConstructorArgumentValues类提供了以下方法:

  • addIndexedArgumentValue (int index, Object value):向容器注册按索引指定的构造函数参数值。
  • addGenericArgumentValue (Object value):向容器注册不按索引指定的构造函数参数值。
  • addGenericArgumentValue (Object value, String type):向容器注册不按索引指定,并且指定了类型的构造函数参数值。
  • getIndexedArgumentValue (int index, Class requiredType):根据索引获取指定类型的构造函数参数值。
  • getGenericArgumentValue (Class requiredType):根据类型获取不按索引指定的构造函数参数值。

通过使用ConstructorArgumentValues类,我们可以在运行时动态地将参数传递给Spring Bean的构造函数,从而实现更灵活和可扩展的配置方式。

BeanDefinitionBuilder

SpringBoot BeanDefinitionBuilder 的介绍以及常用方法 BeanDefinitionBuilder 是 Spring 框架中的一个工具类,用于构建 BeanDefinition 对象。BeanDefinitionBuilder 提供了一些常用的方法,使得创建和配置 BeanDefinition 常用方法包括:

  1. setScope(String scope):设置 bean 的作用域,例如 singleton 或 protot2. setLazyInit(boolean lazyInit):设置 bean 是否延迟初始化3. setAutowired(boolean autowire):设置 bean 是否自动注入依赖4. addConstructorArgValue(Object value):添加构造函数参数值。
  2. addPropertyValue(String name, Object value):添加属性值。
  3. addPropertyReference(String name, String beanName):添加对其他 bean 的引用作为属性值7. setInitMethodName(String initMethodName):设置初始化方法名。
  4. setDestroyMethodName(String destroyMethodName):设置销毁方法名。
使用 BeanDefinitionBuilder 可以通过链式调用这些方法来创建和配置 BeanDefinition 对象。例 builder.setScope("singleton")
       .setLazyInit(true)
       .setAutowired(true)
       .addConstructorArgValue("arg1")
       .addPropertyValue("property1", "value1")
       .addPropertyReference("property2", "otherBean")
       .setInitMethodName("initMethod")
       .setDestroyMethodName("destroyMethod");
       
BeanDefinition beanDefinition = builder.getBeanDefinition();

上述代码创建了一个MyClass类的BeanDefinition对象,并通过各种方法进行了配置。最后调用getBeanDefinition()方法获取到最终的BeanDefinition对象。

SpringBoot 运行时通过 BeanDefinition 手动注册 Bean 对象

要想手动注册 bean 对象,我们需要知道 Spring 是通过哪个类哪个对象去将 bean 注册到 Spring 容器之中的。

BeanFactory 是 Spring 提供的一个接口,内部提供了对 Bean 操作的各种基础方法,在 Spring 实现的默认 Bean 工厂为 DefaultListableBeanFactory

public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory
		implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable

可以看到,该默认实现已经实现了 BeanDefinitionRegistry 接口,我们获取到 BeanFactory 对象后强转为 BeanDefinitionRegistry 就可以拥有手动将 bean 注册到容器中的能力

  • 示例代码
package xyz.tiegangan.chat.common.fileclient.core;

import jodd.util.ClassUtil;
import lombok.SneakyThrows;
import org.springframework.beans.BeansException;
import org.springframework.context.support.GenericApplicationContext;
import xyz.tiegangan.chat.common.fileclient.properties.FileClientProperties;

/**
 * @author huangmuhong
 * @version 1.0.0
 * @date 2023/7/8
 */
public class FileClientInitializationFactory {

  private final GenericApplicationContext applicationContext;

  private final FileClientProperties fileClientProperties;

  public FileClientInitializationFactory(
      GenericApplicationContext applicationContext, FileClientProperties fileClientProperties) {
    this.applicationContext = applicationContext;
    this.fileClientProperties = fileClientProperties;
  }

  @SneakyThrows
  public void fileClientInit() throws BeansException {
    final FileClientProperties.Config config = fileClientProperties.getConfig();
      BeanDefinitionRegistry registry =
	  (BeanDefinitionRegistry) this.applicationContext.getBeanFactory();
    
      final BeanDefinitionBuilder beanDefinitionBuilder =
      BeanDefinitionBuilder.genericBeanDefinition();
      beanDefinitionBuilder.setScope(BeanDefinition.SCOPE_SINGLETON);
    final FileClientEnum type = config.getType();

    final Class<? extends FileClient> fileClientClass = type.getFileClientClass();
    FileClient fileClient = null;

    if (type == FileClientEnum.MINIO) {
       beanDefinitionBuilder.addConstructorArgValue(config);
	    beanDefinitionBuilder.getRawBeanDefinition().setBeanClass(MinioFileClient.class);
    } else {
      throw new IllegalArgumentException("不支持的文件客户端类型:" + config.getType());
    }

        registry.registerBeanDefinition(type.getBeanName(),
     beanDefinitionBuilder.getBeanDefinition());

    return fileClient;
  }
}