@ComponentScan注解如何生效

约定

由于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解析类对应起来的了,就是写死的。
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值