前言

在我们使用Spring Boot的过程中会发现Spring Boot会将启动类同级目录以及子目录下符合条件的类自动注册成Bean,比如带有@Controller,@Service等注解的类。我们今天就看下默认配置在的自动扫描是如何实现的。

简介

Spring Boot在启动过程中会先找出一个基础类,在默认配置下就是我们启动类所在的目录,然后将这个目录下面所有的资源都加载进来,依次遍历每个资源,看他们是否符合特定条件,大体是一个正常的类,并且本类或元注解上带有@Component注解,然后创建并返回这些类的BeanDefinition,供后续创建Bean动作使用。

Spring Boot是如何实现自动扫描的?

扫描的过程

忽略掉前置的流程,我们直接看关键部分的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
Set<BeanDefinition> candidates = new LinkedHashSet<>();
try {
// 确认根路径,默认是启动类所在的包路径
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
resolveBasePackage(basePackage) + '/' + this.resourcePattern;
// 加载所有资源
Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
boolean traceEnabled = logger.isTraceEnabled();
boolean debugEnabled = logger.isDebugEnabled();
for (Resource resource : resources) {
String filename = resource.getFilename();
if (filename != null && filename.contains(ClassUtils.CGLIB_CLASS_SEPARATOR)) {
continue;
}
if (traceEnabled) {
logger.trace("Scanning " + resource);
}
try {
MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
if (isCandidateComponent(metadataReader)) {
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
sbd.setSource(resource);
// 对类再做一些判断,比如类不能是接口或抽象类等等。
if (isCandidateComponent(sbd)) {
if (debugEnabled) {
logger.debug("Identified candidate component class: " + resource);
}
// 收集满足条件的资源类信息
candidates.add(sbd);
}
****************************略过无效代码****************************
return candidates;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
for (TypeFilter tf : this.excludeFilters) {
if (tf.match(metadataReader, getMetadataReaderFactory())) {
return false;
}
}
for (TypeFilter tf : this.includeFilters) {
// 初步匹配
if (tf.match(metadataReader, getMetadataReaderFactory())) {
// 如果类上有@Condition等注解,再判断是否满足条件
return isConditionMatch(metadataReader);
}
}
return false;
}

tf.match是判断这个资源是否匹配,内部调用的是matchSelf方法,这个方法会判断类或元注解上是否有@Component注解,满足条件返回true

1
2
3
4
5
6
7
protected boolean matchSelf(MetadataReader metadataReader) {
AnnotationMetadata metadata = metadataReader.getAnnotationMetadata();
// hasAnnotation(this.annotationType.getName()),类上是有标有指定注解
// hasMetaAnnotation(this.annotationType.getName()),元注解上是否标有指定注解
return metadata.hasAnnotation(this.annotationType.getName()) ||
(this.considerMetaAnnotations && metadata.hasMetaAnnotation(this.annotationType.getName()));
}

总结

总的来说,自动扫描的过程可以总结如下:

  1. 启动时由invokeBeanFactoryPostProcessors方法触发
  2. 通过bean名称从beanfatcory中获取ConfigurationClassPostProcessor,并调用其postProcessBeanDefinitionRegistry方法
  3. 确认basepackages,默认机制下是启动类的包信息
  4. 遍历basepackages,获取单个basepackage下面所有的资源,遍历每个资源,然后为符合条件的资源创建BeanDefinition,这些条件为(全满足):
    • 类上或元注解上标有@Component注解
    • 如果类上标有@Condition条件注解,则必须要满足条件才行
    • 类必须是独立的,比如普通的内部类是不可以的(静态内部类可以认为是独立的)
    • 类不能是接口或抽象类,如果是抽象类,则必须带有@Lookup注解
  5. 返回包含BeanDefinition信息的Set集合