@ComponentScans
注解是组成@SpringBootApplication
注解的之一。主要用于扫描项目中的组件,比如@Compoent``@Service
等等,默认扫描路径是应用程序启动类的同级路径,可以添加应用的自定义路径。
@ComponentScans
注解是在 @Configuration
前被解析。
@ComponentScans
注解是被ComponentScanAnnotationParser#parse()
方法解析的。
ComponentScanAnnotationParser
先简单了解ComponentScanAnnotationParser这个类。
成员变量:
- Environment environment 系统环境
- ResourceLoader resourceLoader 资源加载器
- BeanNameGenerator beanNameGenerator beanName生成策略
- BeanDefinitionRegistry registry bean工厂(DefaultListableBeanFactory)
核心方法
- parse(AnnotationAttributes componentScan, final String declaringClass)
componentScan是@ComponentScan
注解的11个属性。
declaringClass是系统启动类的全限定名。

parse()
方法主要是在构建一个ClassPathBeanDefinitionScanner
类,在方法的最后会调用ClassPathBeanDefinitionScanner#doScan()
方法扫描项目。
其中doScan()方法的传参是扫描路径,默认是启动类的路径,也可以自定义,支持多个路径
//basePackages扫描路径,如:com.kafkaproducer
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
for (String basePackage : basePackages) {
//1.这里会扫描路径下的所有类。如:/Users/XX/kafka-test/kafkaproducer/target/classes/com/kafkaproducer/KafkaProducerTest.class
//2.把资源封装成一个SimpleMetadataReader
//3.根据@ComponentScan的filter判断资源是否满足条件
//4. 满足步骤3的资源,再进行一次筛选。最终筛选出来的类的注解都包含了@Component
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
for (BeanDefinition candidate : candidates) {
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
candidate.setScope(scopeMetadata.getScopeName());
String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
//对扫描出来的candidate的BeanDefinition,设置一些参数。
if (candidate instanceof AbstractBeanDefinition) {
postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
}
if (candidate instanceof AnnotatedBeanDefinition) {
AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
}
//检查beanName在注册表中是否存在(可能会有冲突)
//true:可以注册;false:bean已存在,(beanName相同,beanDefinition也相同)
if (checkCandidate(beanName, candidate)) {
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
//是否通过代理创建
definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
//注册beanDefinition(注册表缓存)
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}
总结
扫描注解@ComponentScans
默认会根据应用程序启动类所在的根路径为默认扫描路径(参考:classpath:com/kafkaproducer/**/.class),也可以通过指定其他路径(比如项目中的DAO层是通过jar包的形式引入进来的,可以指定路径扫描DAO的组件)。负责扫描的类--ClassPathBeanDefinitionScanner
会扫描项目的所有class文件,再经过Filter过滤,得到所有被@Component注解修饰的类。最后把相关类的beanDefiniton存入缓存中。