深入分析Spring BeanDefinition的构造元信息

 更新时间:2024年01月14日 09:02:37   作者:FirstMrRight  
Bean Definition是一个包含Bean元数据的对象,它描述了如何创建Bean实例、Bean属性的值以及Bean之间的依赖关系,本文将带大家深入分析Spring BeanDefinition的构造元信息,需要的朋友可以参考下

Spring BeanDefinition元信息定义方式

Bean Definition是一个包含Bean元数据的对象。它描述了如何创建Bean实例、Bean属性的值以及Bean之间的依赖关系。可以使用多种方式来定义 Bean Definition 元信息,包括:

  • XML 配置文件:使用<bean>标签定义 Bean 元数据,可以指定 Bean 类型、属性值和依赖项等信息。
  • 注解:使用@Component@Service@Repository 等注解标记 Bean 类,并使用 @Autowired注解注入依赖项。
  • Java 配置类:使用@Configuration@Bean注解定义Bean元数据,可以指定 Bean 类型、属性值和依赖项等信息。

除此之外,还可以通过实现 BeanDefinitionRegistryPostProcessor 接口来自定义 Bean Definition 的生成过程。这个接口有一个方法 postProcessBeanDefinitionRegistry(),允许开发人员动态地添加、修改或删除Bean Definition元信息。

XML配置文件定义Bean的元数据

<bean id="user" class="org.thinging.in.spring.ioc.overview.domain.User">
  <property name="id" value="1"/>
  <property name="name" value="Liutx"/>
</bean>

注解定义Bean的元数据

@Service(value = "HelloService")
public class HelloService {

    private final Logger logger = LoggerFactory.getLogger(HelloService.class);

    private final HelloAsyncService helloAsyncService;

        public HelloService(HelloAsyncService helloAsyncService) {
        this.helloAsyncService = helloAsyncService;
    }
}

value = "HelloService" 即为Bean:HelloService的元数据,在构造方法中的依赖关系同样属于元数据。

Java 配置类定义Bean的元数据

@Component(value = "balanceRedisProcessor")
public class BalanceRedisProcessorService implements EntryHandler<Balance>, Runnable {

    @Autowired(required = true)
    public BalanceRedisProcessorService(RedisUtils redisUtils,
                                        CanalConfig canalConfig,
                                        @Qualifier("ownThreadPoolExecutor") Executor executor, RocketMQProducer rocketMqProducer) {
        this.redisUtils = redisUtils;
        this.canalConfig = canalConfig;
        this.executor = executor;
        this.rocketMQProducer = rocketMqProducer;
    }
}

@Component(value = "balanceRedisProcessor") 是Bean:BalanceRedisProcessorService的元数据,在构造方法中的依赖关系同样属于元数据。

BeanDefinition的元数据解析

在Spring中,无论是通过XML、注解、Java配置类定义Bean元数据,最终都是需要转换成BeanDefinition对象,然后被注册到Spring容器中。

BeanDefinition的创建过程,确实是通过AbstractBeanDefinition及其派生类、``等一系列工具类实现的。

  • 当我们使用XML配置时,Spring会解析XML文件,将其中的Bean元数据信息转换成对应的BeanDefinition对象,然后注册到Spring容器中。在这个过程中,Spring内部会使用XmlBeanDefinitionReader等相关工具类,将XML文件中定义的Bean元数据转换成BeanDefinition对象。
  • 当我们使用注解方式或Java配置类方式定义Bean元数据时,Spring会扫描相应的注解或Java配置类,然后根据其定义生成对应的BeanDefinition对象,并注册到Spring容器中。在这个过程中,Spring内部会使用AnnotationConfigApplicationContext等相关工具类,将注解或Java配置类中定义的Bean元数据转换成BeanDefinition对象。

源码分析XML是如何转化为Spring BeanDefinition的

将xml文件中的配置转为为BeanDefinition需要依赖自XmlBeanDefinitionReader类中的loadBeanDefinitions方法。

选自:Spring Framework 5.2.20 RELEASE版本的XmlBeanDefinitionReader

private final ThreadLocal<Set<EncodedResource>> resourcesCurrentlyBeingLoaded =
        new NamedThreadLocal<Set<EncodedResource>>("XML bean definition resources currently being loaded"){
            @Override
            protected Set<EncodedResource> initialValue() {
                return new HashSet<>(4);
            }
        };

/**
 * Load bean definitions from the specified XML file.
 * @param encodedResource the resource descriptor for the XML file,
 * allowing to specify an encoding to use for parsing the file
 * @return the number of bean definitions found
 * @throws BeanDefinitionStoreException in case of loading or parsing errors
 */
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
    Assert.notNull(encodedResource, "EncodedResource must not be null");
    if (logger.isTraceEnabled()) {
        logger.trace("Loading XML bean definitions from " + encodedResource);
    }

    Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();

    if (!currentResources.add(encodedResource)) {
        throw new BeanDefinitionStoreException(
                "Detected cyclic loading of " + encodedResource + " - check your import definitions!");
    }

    try (InputStream inputStream = encodedResource.getResource().getInputStream()) {
        InputSource inputSource = new InputSource(inputStream);
        if (encodedResource.getEncoding() != null) {
            inputSource.setEncoding(encodedResource.getEncoding());
        }
        // 实际上从指定的 XML 文件加载 Bean 定义
        return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
    }
    catch (IOException ex) {
        throw new BeanDefinitionStoreException(
                "IOException parsing XML document from " + encodedResource.getResource(), ex);
    }
    finally {
        currentResources.remove(encodedResource);
        if (currentResources.isEmpty()) {
            this.resourcesCurrentlyBeingLoaded.remove();
        }
    }
}


//实际上从指定的 XML 文件加载 Bean 定义
/**
 * Actually load bean definitions from the specified XML file.
 * @param inputSource the SAX InputSource to read from
 * @param resource the resource descriptor for the XML file
 * @return the number of bean definitions found
 * @throws BeanDefinitionStoreException in case of loading or parsing errors
 * @see #doLoadDocument
 * @see #registerBeanDefinitions
 */
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
        throws BeanDefinitionStoreException {

    try {
        Document doc = doLoadDocument(inputSource, resource);
        int count = registerBeanDefinitions(doc, resource);
        if (logger.isDebugEnabled()) {
            logger.debug("Loaded " + count + " bean definitions from " + resource);
        }
        return count;
    }
}
  • 使用ThreadLocal线程级别的变量存储带有编码资源的集合,保证每个线程都可以访问到XmlBeanDefinitionReader在加载XML配置文件时当前正在加载的资源,以确保加载过程中的完整性和正确性。
  • ThreadLocal中获取到当前正在加载的xml资源,转换为输入流
  • 开始执行doLoadBeanDefinitions,实际上从指定的 XML 文件加载 Bean 定义,该方法会返回加载的Bean定义数量。
  • doLoadBeanDefinitions方法中,首先调用doLoadDocument方法加载XML文件并生成一个Document对象。
  • 然后,调用registerBeanDefinitions方法来注册Bean定义,将其放入Spring容器中。该方法会返回注册的Bean定义数量。
  • 最后,根据需要记录日志信息,并返回加载的Bean定义数量。

源码分析配置类、注解是如何转化为Spring BeanDefinition的

在Spring中,配置类和注解都可以被转换为Bean定义(BeanDefinition)。下面是关于如何将配置类和注解转换为Bean定义的简要源码分析:

  • 配置类转换为Bean定义:
    • 当使用Java配置类时,Spring会通过解析配置类中的注解来生成相应的Bean定义。主要实现是通过ConfigurationClassParser类完成的。
    • ConfigurationClassParser会解析配置类上的注解,包括@Configuration@ComponentScan@Bean等,然后将其转换为对应的Bean定义。
    • 在解析过程中,Spring会创建一个ConfigurationClass对象表示配置类,并根据不同的注解类型生成相应的Bean定义,包括RootBeanDefinitionMethodMetadata
    • RootBeanDefinition代表配置类本身,而MethodMetadata代表配置类中的方法上的注解,例如@Bean注解。
    • 最终,这些生成的Bean定义会被注册到DefaultListableBeanFactory中,以供后续的Bean实例化和依赖注入。
  • 注解转换为Bean定义:
    • 当使用注解方式配置Bean时,Spring会扫描指定的包或类,并解析其中的注解来生成Bean定义。
    • Spring提供了AnnotationBeanDefinitionReader类用于处理注解,它会扫描指定的包路径或类,并根据注解生成相应的Bean定义。
    • 在扫描过程中,AnnotationBeanDefinitionReader会解析常见的注解,比如@Component@Controller@Service@Repository等,然后生成相应的Bean定义。
    • 注解生成的Bean定义同样会被注册到DefaultListableBeanFactory中,以供后续的Bean实例化和依赖注入。

总而言之,无论是配置类还是注解,Spring都会通过解析注解并生成对应的Bean定义,最终将这些Bean定义注册到DefaultListableBeanFactory中。这样,在容器启动时,Spring就能够根据这些Bean定义来实例化Bean并进行依赖注入。

配置类、注解转换为Spring BeanDefition源码后续博客中展示,敬请期待。

如何手动构造BeanDefinition

Bean定义

public class User {

    private Long id;

    private String name;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + ''' +
                '}';
    }
}

通过BeanDefinitionBuilder构建

//通过BeanDefinitionBuilder构建
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(User.class);
//通过属性设置
beanDefinitionBuilder.addPropertyValue("id", 1L)
        .addPropertyValue("name","公众号:种棵代码技术树");

//获取BeanDefinition实例
BeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition();
// BeanDefinition 并非 Bean 终态,可以自定义修改
System.out.println(beanDefinition);

通过AbstractBeanDefinition以及派生类

// 2. 通过 AbstractBeanDefinition 以及派生类
GenericBeanDefinition genericBeanDefinition = new GenericBeanDefinition();
//设置Bean类型
genericBeanDefinition.setBeanClass(User.class);
//通过 MutablePropertyValues 批量操作属性
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.add("id",1L)
        .add("name","公众号:种棵代码技术树");
// 通过 set MutablePropertyValues 批量操作属性
genericBeanDefinition.setPropertyValues(propertyValues);

最后

以上就是深入分析Spring BeanDefinition的构造元信息的详细内容,更多关于Spring BeanDefinition构造元信息的资料请关注脚本之家其它相关文章!

相关文章

  • 详解Java的Struts框架中上传文件和客户端验证的实现

    详解Java的Struts框架中上传文件和客户端验证的实现

    这篇文章主要介绍了Java的Struts框架中上传文件和客户端验证的实现,Struts是Java的SSH三大web开发框架之一,需要的朋友可以参考下
    2015-12-12
  • MyBatis获取插入记录的自增长字段值(ID)

    MyBatis获取插入记录的自增长字段值(ID)

    本文分步骤给大家介绍了MyBatis获取插入记录的自增长字段值的方法,在文中给大家提到了mybatis返回插入数据的自增长id,需要的朋友可以参考下
    2017-11-11
  • SpringBoot注册第三方Bean的方法总结

    SpringBoot注册第三方Bean的方法总结

    众所周知,SpringBoot默认会扫描启动类所在的包及其子包,一般我们都是在需要的类上通过注解的方式去将Bean注册交给IOC进行管理,但是注册第三方Bean的方案却不支持,所以本文给大家介绍了SpringBoot注册第三方Bean的方法,需要的朋友可以参考下
    2024-01-01
  • 如何解决Java多线程死锁问题

    如何解决Java多线程死锁问题

    死锁是一个很严重的、必须要引起重视的问题,本文主要介绍了死锁的定义,解决方法和面试会遇到的问题,感兴趣的可以了解一下
    2021-05-05
  • java字符串切割实例学习(获取文件名)

    java字符串切割实例学习(获取文件名)

    在Java中处理一些路径相关的问题的时候,如要取出ie浏览器上传文件的文件名,由于ie会把整个文件路径都作为文件名上传,需要用java.lang.String中的replaceAll或者split来处理,下面看看使用方法
    2013-12-12
  • java批量导入Excel数据超详细实例

    java批量导入Excel数据超详细实例

    这篇文章主要给大家介绍了关于java批量导入Excel数据的相关资料,EXCEL导入就是文件导入,操作代码是一样的,文中给出了详细的代码示例,需要的朋友可以参考下
    2023-08-08
  • java 根据经纬度获取地址实现代码

    java 根据经纬度获取地址实现代码

    这篇文章主要介绍了 java 根据经纬度获取地址实现代码的相关资料,需要的朋友可以参考下
    2017-05-05
  • MyBatis实现动态SQL的实现方法

    MyBatis实现动态SQL的实现方法

    这篇文章主要介绍了MyBatis实现动态SQL的实现方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-12-12
  • 详解MyBatis Mapper 代理实现数据库调用原理

    详解MyBatis Mapper 代理实现数据库调用原理

    这篇文章主要介绍了详解MyBatis Mapper 代理实现数据库调用原理,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-10-10
  • Java使用Freemarker页面静态化生成的实现

    Java使用Freemarker页面静态化生成的实现

    这篇文章主要介绍了Java使用Freemarker页面静态化生成的实现,页面静态化是将原来的动态网页改为通过静态化技术生成的静态网页,FreeMarker 是一个用 Java 语言编写的模板引擎,它基于模板来生成文本输,更多相关内容需要的小伙伴可以参考一下
    2022-06-06

最新评论