人人都能看懂的Spring源码解析,扫描加载BeanDefinition的过程
- 原理解析
- 什么是BeanDefinition?
- 两种配置方式
- 扫描并读取配置信息,解析成BeanDefinition
- 保存BeanDefinition
- 源码走读
- xml配置方式
- 整体流程
- 示例代码
- BeanDefinition加载解析的入口
- 创建XmlBeanDefinitionReader,加载配置文件
- 配置文件加载成Document,遍历解析里面的标签
- 解析标签,生成BeanDefinition
- 注册BeanDefinition到容器
- 注解配置方式
- 整体流程
- 示例代码
- 创建AnnotatedBeanDefinitionReader
- 注册ConfigurationClassPostProcessor
- 解析启动配置类为BeanDefinition,并注册到容器中
- 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)
调用了AnnotatedBeanDefinitionReader的register方法,解析配置类为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注册到容器中。