Spring创建BeanDefinition之路径扫描详解
作者:程序员侠客行
一、从示例开始
当我们创建AnnotationConfigApplicationContext对象时,Spring底层到底做了些什么?
来看下面示例。
package com.xiakexing; import com.xiakexing.service.UserService; import org.springframework.context.annotation.AnnotationConfigApplicationContext; public class Test { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); UserService userService = context.getBean("userService", UserService.class); userService.test(); } }
package com.xiakexing; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @Configuration @ComponentScan(value = "com.xiakexing") public class AppConfig { }
package com.xiakexing.service; import org.springframework.stereotype.Component; @Component public class UserService { public void test() { System.out.println("hello spring"); } }
我们猜测这几行代码执行逻辑:
- new AnnotationConfigApplicationContext(AppConfig.class)时,从AppConfig类解析扫描路径即@ComponentScan;
- 遍历扫描路径下的所有Java类,如果某个类上有@Component、@Service等注解,Spring就为这个类创建BeanDefinition,保存到Map中,比如Map<String, Class>,key是根据规则生成的beanName,value就是当前类的class对象。
- context.getBean("userService")时,Spring根据beanName找到类的class对象,反射调用构造器创建对象。
带着上面的猜想,我们来看看源码。本文暂且关注路径扫描的实现,随后的文章将讲解BeanDefinition的创建过程。
二、创建AnnotationConfigApplicationContext
构造方法的入参是componentClasses,即可以传入多个配置类。
this()中创建了AnnotatedBeanDefinitionReader、ClassPathBeanDefinitionScanner,将用于扫描指定路径下的类,创建BeanDefinition。
JFR 是 Java Flight Record (Java飞行记录),是JVM 内置的基于事件的JDK监控记录框架。StartupStep是Spring基于JFR对运行过程的监控,阅读源码时可忽略它。
注意,AnnotationConfigApplicationContext间接实现了BeanDefinitionRegistry接口,具备向容器中注册BeanDefinition的能力。
在创建ClassPathBeanDefinitionScanner对象时,指定了使用DefaultFilters:将扫描所有带有@Component注解的类。
总结:this()仅仅实例化了容器对象,创建了Reader、Scanner,用于解析类信息。
三、注册Configuration类
3.1 创建BeanDefinition
register(componentClasses),显然是将配置类注册到容器中。
来看AnnotatedBeanDefinitionReader#doRegisterBean的核心逻辑:
- 为配置类创建AnnotatedGenericBeanDefinition对象;
- 处理@Conditional,如果条件不满足,将舍弃这个类;
- 给BeanDefinition对象属性赋值;
- 生成beanName,解析@Lazy、@Primary、@DependsOn等注解;
- 创建BeanDefinitionHolder对象,发起注册。
3.2 注册BeanDefinition
BeanDefinitionHolder类只是对BeanDefinition的包装,仅有三个属性:beanDefinition、beanName和aliases。
在BeanDefinitionReaderUtils#registerBeanDefinition中
最终会调用DefaultListableBeanFactory#registerBeanDefinition方法,执行这几行代码:
看到了BeanFactory的两个核心数据结构:
- beanDefinitionMap保存了beanName与beanDefinition的映射;
- beanDefinitionNames保存了所有的beanName
果然与我们当初的猜想一致。
至此,配置类已被添加到beanDefinitionMap中,可是@ComponentScan指定的包路径,在哪儿被处理了呢?
四、扫描包路径
先说结论:@ComponentScan包路径下的类,是在ClassPathBeanDefinitionScanner#scan中被处理的。
先来看AnnotationConfigApplicationContext的另一个构造方法:入参就是指定包路径。
转调到ClassPathBeanDefinitionScanner#scan。
基于配置类创建AnnotationConfigApplicationContext时,是在哪儿调了scan()或doScan()呢?答案就在refresh()中。
4.1 BeanFactoryPostProcessor接口
先看类注释:
Factory hook that allows for custom modification of an application context's bean definitions, adapting the bean property values of the context's underlying bean factory.
工厂钩子,允许自定义修改应用程序上下文的bean定义,调整上下文的底层bean工厂的bean属性值。
A BeanFactoryPostProcessor may interact with and modify bean definitions, but never bean instances.
BeanFactoryPostProcessor可以与bean定义交互和修改,但不能与bean实例交互。
可见,该接口是BeanFactory的后置处理器,在创建Bean实例前,干涉BeanDefinition创建和更新。
仅有一个方法:
void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;
该接口有个重要的子接口BeanDefinitionRegistryPostProcessor,能够向容器注册更多的BeanDefinition。
Extension to the standard BeanFactoryPostProcessor SPI, allowing for the registration of further bean definitions before regular BeanFactoryPostProcessor detection kicks in.
对标准BeanFactoryPostProcessor SPI的扩展,允许在常规BeanFactoryPostProcessor检测开始之前注册进一步的bean定义。
4.2 ConfigurationClassPostProcessor类
源码中,BeanDefinitionRegistryPostProcessor接口仅有唯一实现ConfigurationClassPostProcessor。
在ConfigurationClassPostProcessor#processConfigBeanDefinitions中,
检查已注册的每一个BeanDefinition,是否是候选配置类(或组件),满足以下任意条件即可:
- 类上有@Configuration注解;
- 类上有以下任意一个注解;
- 类中有@Bean注解的方法;
得到Set<BeanDefinitionHolder> candidates后,会调用ConfigurationClassParser.parse()
接下来会遍历处理每一个候选类
在ConfigurationClassParser#doProcessConfigurationClass中,解析@ComponentScan、@ComponentScans注解;
- 执行Filter逻辑后,得到basePackages路径集;
- 调用ClassPathBeanDefinitionScanner#doScan,为路径下的Bean创建BeanDefinition,并注册到容器中。
关于doScan方法的详细逻辑,我们下一篇再看。
五、逻辑闭环
要用ConfigurationClassPostProcessor来处理配置类,Spring容器中就得先有该类的实例。那么,这个类是何时注册到容器中的?
答案就在new AnnotatedBeanDefinitionReader(this)中:
为ConfigurationClassPostProcessor创建BeanDefinition并注册。
当执行AbstractApplicationContext#refresh时,其中有一步是调用容器中BeanFactoryPostProcessor接口所有实现。
此时,ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry将被执行。
流程图
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。
您可能感兴趣的文章:
- Spring中ImportBeanDefinitionRegistrar源码和使用方式
- 解析Spring框架中的XmlBeanDefinitionStoreException异常情况
- 解析和解决org.springframework.beans.factory.NoSuchBeanDefinitionException异常问题
- SpringBoot实现ImportBeanDefinitionRegistrar动态注入
- Spring配置文件解析之BeanDefinitionParserDelegate详解
- Spring配置文件解析之BeanDefinitionDocumentReader详解
- Spring配置文件解析之BeanDefinitionReader详解