人人都能看懂的Spring源码解析,配置解析与BeanDefinition加载注册

本文详细解析了Spring框架中BeanDefinition的创建过程,包括XML配置和注解配置两种方式。从配置文件或配置类读取信息,解析成BeanDefinition,并存入Spring容器。文章通过源码走读,展示了从配置到BeanDefinition的转化步骤,涉及XmlBeanDefinitionReader、AnnotatedBeanDefinitionReader以及ConfigurationClassPostProcessor等关键组件。

每一位看到本篇文章的人,你们好,我是黄俊懿。

之前写过两篇关于Spring原理解析的文章,是以对新手友好的出发点去写的,以画图的形式进行讲解,没有对Spring的源码进行详细的解析,目的是希望一些没有看过Spring源码的小伙伴能够很好的理解。

人人都能看懂的Spring底层原理,看完绝对不会懵逼

简单易懂的Spring扩展点详细解析,看不懂你来打我

然后从本篇文章开始,我打算写一些关于Spring源码解析的内容,也是尽量做到对新手友好,当然看过Spring源码的小伙伴也可以拿来作为复习。

希望能坚持下去,帮助大家学习或者复习Spring的知识,同时自己也有所收获。

本篇文章是关于配置解析与BeanDefinition加载注册的源码解析,也就是从xml配置或者注解配置被解析为BeanDefinition放入容器中的这个过程。

在这里插入图片描述

原理解析

什么是BeanDefinition?

相当于是bean的设计图纸,Spring要实例化和初始化bean,需要先有bean的设计图纸,好比建房子之前,先要有房子的设计图纸。

在这里插入图片描述

而BeanDefinition中保存了bean的各种配置属性,Spring会根据其中的配置属性,去实例化和初始化bean。

在这里插入图片描述

两种配置方式

我们可以通过两种方式进行配置:

  • xml
  • 注解

在这里插入图片描述

不管使用哪种配置方式,在创建Spring应用上下文的时候,都要指定配置解析的入口。如果是xml配置方式,配置解析的入口就是xml配置文件,如果是注解配置方式,配置解析入口就是 @Configuration注解修饰的配置类

扫描并读取配置信息,解析成BeanDefinition

在创建Spring应用上下文的时,指定了配置文件或者配置类之后,接下来就是扫描并读取配置信息,解析成BeanDefinition

在Spring内部,定义了一个组件,专门负责bean定义配置的扫描解析,并将bean的配置信息解析成BeanDefinition,然后放入容器中,那就是BeanDefinitionReader

在这里插入图片描述

保存BeanDefinition

扫描完配置,解析成BeanDefinition之后,就要把这些BeanDefinition保存到Spring容器中。

Spring内部定义了另外一个组件BeanDefinitionRegistry(bean定义注册表),用于注册BeanDefinition,而实现类就是DefaultListableBeanFactory,使用一个Map结构存放解析出来的BeanDefinition,key是String类型的beanName,value是BeanDefinition类型

在这里插入图片描述

DefaultListableBeanFactory中关于BeanDefinition的重要属性和方法:

  • beanDefinitionMap就是用于存放注册进来的BeanDefinition的哈希表
  • beanDefinitionNames用于存放注册进来的BeanDefinition的beanName
  • registerBeanDefinition(String, BeanDefinition) 就是用于注册BeanDefinition的方法

源码走读

接下来是源码走读,看一下Spring里面源码的调用流程。

注意:代码调用流程并不重要,不需要硬记,只要知道里面大概干了啥就行,代码调用流程只是用于证明里面确实是这么干的。

xml配置方式

整体流程

在这里插入图片描述

上面这幅图是xml配置方式BeanDefinition解析的整体流程,每一步用不同颜色标记,防止迷路。

示例代码

com.demo.xml.Main

public class Main {

    public static void main(String[] args) {

        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        Person person = context.getBean(Person.class);
        System.out.println(person);

    }

}

beans.xml

<beans ...">

    <bean id="person" class="com.demo.Person"/>
    
</beans>

com.demo.Person

public class Person {
}

BeanDefinition加载解析的入口

在这里插入图片描述

代码调用流程:ClassPathXmlApplicationContext的构造方法 => refresh() => obtainFreshBeanFactory() => loadBeanDefinition(beanFactory)

在这里插入图片描述

loadBeanDefinition(beanFactory) 方法就是BeanDefinition加载解析的入口。

创建XmlBeanDefinitionReader,加载配置文件

在这里插入图片描述

代码调用流程:创建XmlBeanDefinitionReader,调用loadBeanDefinitions(beanDefinitionReader) => reader.loadBeanDefinitions(configLocations) => 读取配置文件,以流的形式加载到内存,doLoadBeanDefinitions(inputSource, encodedResource.getResource())

在这里插入图片描述

可以看到,loadBeanDefinition(beanFactory)方法方法里面就是创建了一个XmlBeanDefinitionReader,并调用XmlBeanDefinitionReader的loadBeanDefinitions方法,里面读取配置文件并加载到内存。

配置文件加载成Document,遍历解析里面的标签

在这里插入图片描述
代码调用流程:加载成Document,调用registerBeanDefinitions(doc, resource) => 创建BeanDefinitionDocumentReader,调用documentReader.registerBeanDefinitions(doc, createReaderContext(resource)) => 获取Document里面的根标签,调用doRegisterBeanDefinitions(…) => 创建BeanDefinitionParserDelegate,调用parseBeanDefinitions(root, this.delegate) => 获取根标签下的所有子标签,然后遍历所有子标签,进行解析

在这里插入图片描述

XmlBeanDefinitionReader里面,就是把配置文件加载成Document对象,然后获取里面的根标签(也就是<beans>标签),然后获取根标签下的所有子标签,遍历所有的子标签(<bean>标签),进行解析

中间创建的BeanDefinitionParserDelegate对象,是用于后面解析标签为BeanDefinition的。

解析标签,生成BeanDefinition

在这里插入图片描述

代码调用流程:processBeanDefinition(ele, delegate) => delegate.parseBeanDefinitionElement(ele) => parseBeanDefinitionElement(ele, beanName, containingBean) => createBeanDefinition(className, parent) => BeanDefinitionReaderUtils.createBeanDefinition(…) => new GenericBeanDefinition()

在这里插入图片描述

可以看到,最后创建的BeanDefinition类型是GenericBeanDefinition,然后解析标签上的各种属性,赋值到BeanDefinition对应的属性上。

注册BeanDefinition到容器

在这里插入图片描述

代码调用流程:BeanDefinitionReaderUtils.registerBeanDefinition(…) => registry.registerBeanDefinition(…) =>
this.beanDefinitionMap.put(beanName, beanDefinition)

在这里插入图片描述

可以看到,里面调用的时BeanDefinitionRegistry的registerBeanDefinition方法,进入到DefaultListableBeanFactory#registerBeanDefinition方法里面,把BeanDefinition放入到DefaultListableBeanFactory里面的beanDefinitionMap属性中,该属性是一个Map类型,key是beanName,value是BeanDefinition

注解配置方式

整体流程

在这里插入图片描述

上面这幅图是注解配置方式BeanDefinition解析的整体流程,每一步用不同颜色标记,防止迷路。

示例代码

com.demo.annotation.Main

public class Main {

    public static void main(String[] args) {

        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig.class);
        Person person = context.getBean(Person.class);
        System.out.println(person);

    }

}

com.demo.annotation.MainConfig

@Configuration
public class MainConfig {
    @Bean
    public Person person() {
        return new Person();
    }
}

创建AnnotatedBeanDefinitionReader

在这里插入图片描述

代码调用流程:AnnotationConfigApplicationContext构造方法 => this() => 创建AnnotatedBeanDefinitionReader

在这里插入图片描述

AnnotatedBeanDefinitionReader也是一个BeanDefinitionReader,主要是针对注解版配置的解析,也具有解析配置信息为BeanDefinition并注册到容器的功能,但其实它没有实现BeanDefinitionReader接口。

注册ConfigurationClassPostProcessor

在这里插入图片描述

代码调用流程:AnnotatedBeanDefinitionReader构造方法 => this(…) => AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry) => 注册ConfigurationClassPostProcessor到容器中

在这里插入图片描述

ConfigurationClassPostProcessor的作用就是解析@Configuration注解修饰的配置类里的配置信息,生成BeanDefinition,注册到容器中。

@Configuration注解修饰的配置类,相当于xml配置方式下的xml配置文件。

解析启动配置类为BeanDefinition,并注册到容器中

在这里插入图片描述

代码调用流程:register(componentClasses) => this.reader.register(componentClasses)

在这里插入图片描述

调用了AnnotatedBeanDefinitionReaderregister方法,解析配置类为BeanDefinition,并注册到容器中。

在这里插入图片描述

代码调用流程:registerBean(componentClass) => doRegisterBean(…) => 创建AnnotatedGenericBeanDefinition类型的BeanDefinition,并配置该BeanDefinition,然后注册到容器中

在这里插入图片描述

这里就是根据参数传进来的配置类的类型,创建BeanDefinition,BeanDefinition的类型是AnnotatedGenericBeanDefinition,是注解版的BeanDefinition,然后对其进行属性配置,最后注册到容器中。

ConfigurationClassPostProcessor,注解版配置信息的解析入口

在这里插入图片描述

代码调用流程:refresh() => invokeBeanFactoryPostProcessors(beanFactory) => PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors()) => invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry) => postProcessor.postProcessBeanDefinitionRegistry(registry) => ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry

在这里插入图片描述

前面已经把@Configuration注解修饰的启动配置类,解析成BeanDefinition,注册到了Spring容器中,那么接下来就要对启动配置类里的配置信息进行解析,而解析入口,就是ConfigurationClassPostProcessor的postProcessBeanDefinitionRegistry方法

配置信息解析

在这里插入图片描述

ConfigurationClassPostProcessor实现了BeanDefinitionRegistryPostProcessor接口,BeanDefinitionRegistryPostProcessor又继承了BeanFactoryPostProcessor接口,所以ConfigurationClassPostProcessor也是一个Bean工厂后置处理器

在这里插入图片描述

ConfigurationClassPostProcessor实现了BeanDefinitionRegistryPostProcessor接口的postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)方法,该方法可以往Spring容器中注册一些BeanDefinition。ConfigurationClassPostProcessor将会在该方法中解析@Configuration注解修饰的配置类里的配置信息,注册BeanDefinition到Spring容器中,如果遇到了@ComponentScan注解,则会进行包扫描,解析@Component注解修饰的类为BeanDefinition,注册到容器中,如果又遇到了其他被@Configuration注解修饰的类,会继续进行解析

在这里插入图片描述

但是因为ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry里面的逻辑比较复杂,解析里面的逻辑可能需要大量的篇幅,所以我感觉单独开一篇博客进行解析会比较好,所以这篇博客就到这里结束吧,ConfigurationClassPostProcessor里面的源码解析将会放到下一篇博客。

总结

在这里插入图片描述

最后总结一下:xml配置方式由XmlBeanDefinitionReader进行配置文件的解析与BeanDefinition的注册,而注解配置方式则由AnnotatedBeanDefinitionReader将启动配置类解析成BeanDefinition注册到容器中,再由ConfigurationClassPostProcessor进行@Configuration注解修饰的配置类的配置信息解析,生成BeanDefinition注册到容器中。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值