约定
由于Spring类名普遍很长,为了方便阅读,类名第一次出现时会以全限定名称出现,即包名+类名的形式,第二次开始只会写类名;方法名类似,第一次出现时,会以类名+方法名的形式,第二次开始只会写方法名(同名方法例外,会一直使用类名+方法名形式)
前提了解
org.springframework.context.annotation.AnnotationConfigUtils这个类注册了很多Spring用到的PostProcessor组件,其中包括org.springframework.context.annotation.ConfigurationClassPostProcessor
ConfigurationClassPostProcessor说明
ConfigurationClassPostProcessor顾名思义就是用来处理Configuration类的后置处理器,既然是后置处理器,那按照Spring使用方式,那必定也是在bean初始化过程中回调
通过查看其继承体系,可以看到其实现了org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor接口,而BeanDefinitionRegistryPostProcessor又继承了org.springframework.beans.factory.config.BeanFactoryPostProcessor
大致结构如下:
ConfigurationClassPostProcessor -implement-> BeanDefinitionRegistryPostProcessor -extends-> BeanFactoryPostProcessor
BeanDefinitionRegistryPostProcessor、BeanFactoryPostProcessor这两个接口,每个接口都只定义了一个方法,分别是:
- BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry
- BeanFactoryPostProcessor#postProcessBeanFactory
而在Spring Bean初始化过程中,postProcessBeanDefinitionRegistry回调早于postProcessBeanFactory,所以接下来我们先分析postProcessBeanDefinitionRegistry
流程分析
1. ConfigurationClassPostProcessor
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
// 省略部分代码
processConfigBeanDefinitions(registry);
}
这里postProcessBeanDefinitionRegistry是调用了processConfigBeanDefinitions方法
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
// 省略部分代码
// Parse each @Configuration class
ConfigurationClassParser parser = new ConfigurationClassParser(
this.metadataReaderFactory, this.problemReporter, this.environment,
this.resourceLoader, this.componentScanBeanNameGenerator, registry);
Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
do {
// 这里只关注主要逻辑,即Configuration类的解析过程,所以省略了部分代码,省略的代码中申明了Set<ConfigurationClass>变量candidates ,里面元素都是标注了@Configuration注解的类
parser.parse(candidates);
// snipped
}
}
processConfigBeanDefinitions方法新建了ConfigurationClassParser对象,然后调用了org.springframework.context.annotation.ConfigurationClassParser#parse方法,入参是标注了@Configuration注解的类
public void parse(Set<BeanDefinitionHolder> configCandidates) {
for (BeanDefinitionHolder holder : configCandidates) {
BeanDefinition bd = holder.getBeanDefinition();
// 省略部分代码
if (bd instanceof AnnotatedBeanDefinition) {
parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
}
// 省略部分代码
}
this.deferredImportSelectorHandler.process();
}
2. ConfigurationClassParser
可以看到parse方法调用了ConfigurationClassParser#parse重载方法
protected final void parse(@Nullable String className, String beanName) throws IOException {
Assert.notNull(className, "No bean class name for configuration class bean definition");
MetadataReader reader = this.metadataReaderFactory.getMetadataReader(className);
processConfigurationClass(new ConfigurationClass(reader, beanName), DEFAULT_EXCLUSION_FILTER);
}
protected void processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) throws IOException {
// 省略部分代码
do {
sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter);
}
while (sourceClass != null);
}
protected final SourceClass doProcessConfigurationClass(
ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
throws IOException {
// 省略部分代码
// Process any @ComponentScan annotations
// 获取类上@ComponentScan注解的元信息
Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
// 过滤标注了@ComponentScan注解的类
if (!componentScans.isEmpty() &&
!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
for (AnnotationAttributes componentScan : componentScans) {
// The config class is annotated with @ComponentScan -> perform the scan immediately
// 解析@ComponentScan注解
Set<BeanDefinitionHolder> scannedBeanDefinitions =
this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
// 省略部分代码
}
}
// 省略部分代码
return null;
}
重载方法调用了processConfigurationClass,processConfigurationClass又调用了ConfigurationClassParser#doProcessConfigurationClass,
doProcessConfigurationClass先是获取类上的@ComponentScan注解信息,如果有,则会调用
org.springframework.context.annotation.ComponentScanAnnotationParser#parse方法进行解析,而ComponentScanAnnotationParser#parse正是解析@ComponentScan注解所在。
那ComponentScanAnnotationParser对象是怎么来的呢?答案是:在创建ConfigurationClassParser对象时创建的,而且是直接通过new的方式写死的,并没有提供set方法
public ConfigurationClassParser(MetadataReaderFactory metadataReaderFactory,
ProblemReporter problemReporter, Environment environment, ResourceLoader resourceLoader,
BeanNameGenerator componentScanBeanNameGenerator, BeanDefinitionRegistry registry) {
// 省略部分代码
this.componentScanParser = new ComponentScanAnnotationParser(
environment, resourceLoader, componentScanBeanNameGenerator, registry);
}
小结
通过以上分析应该就知道了@ComponentScan注解是如何与ComponentScanAnnotationParser解析类对应起来的了,答案是:通过在ConfigurationClassParser#doProcessConfigurationClass取出出类上@ComponentScan注解的元信息,如果@ComponentScan注解元信息存在,就交给ComponentScanAnnotationParser#parse去解析。
而ComponentScanAnnotationParser是在ConfigurationClassParser构造器里直接new 出来的。这样@ComponentScan就和ComponentScanAnnotationParser联系起来了。
而ConfigurationClassParser则是在Spring Bean初始化时回调BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry阶段创建
Debug分析
以上从代码层面分析了执行流程,但是直接阅读代码很容易绕晕,涉及到多态的情况下可能还会看错类。这种情况下可以借助IDE来更高效清晰的查看调用栈,比如针对以上流程,我们先是知道了ComponentScanAnnotationParser是@ComponentScan注解的解析器,所以可以在ComponentScanAnnotationParser#parse方法打断点,然后在IDE里看调用栈
可以很清晰的看到方法调用链,这样就可以一层一层倒推了。
比如这里ComponentScanAnnotationParser#parse方法是在ConfigurationClassParser#doProcessConfigurationClass调用的,并且componentScanParser是ConfigurationClassParser的一个成员变量,那我们就可以查看componentScanParser是在哪创建的
通过搜索可以看到componentScanParser是在ConfigurationClassParser构造器里创建的,所以也就明白@ComponentScan注解是如何和ComponentScanAnnotationParser解析类对应起来的了,就是写死的。