我在分析BeanDefnition扫描过程中,有下面这样一段源码:
org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#findCandidateComponents
public Set<BeanDefinition> findCandidateComponents(String basePackage) {
if (this.componentsIndex != null && indexSupportsIncludeFilters()) {
return addCandidateComponentsFromIndex(this.componentsIndex, basePackage);
}
else {
return scanCandidateComponents(basePackage);
}
}今天我们就来分析下componentsIndex的用法与原理。
ComponentsIndex机制的使用
ComponentsIndex顾名思义,就是对扫描BeanDefinition做的一个索引,通过META-INF/spring.components文件,我们可以定义要扫描哪些类,从而不去扫描包下面的所有类。
如果spring.components文件中有内容,那么扫描时就只会扫描文件中的类,并进行过滤,得到最终的BeanDefinition,这样能提高扫描的效率。
META-INF/spring.components文件中的内容
com.hexon.service.UserService=org.springframework.stereotype.Component业务类及测试类如下:
@Component
public class UserService {
public void test() {
System.out.println("UserService#test....");
}
}@Component
public class OrderService {
}public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class);
applicationContext.getBean(OrderService.class);
}
}报错:
Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.hexon.service我们可以通过断点查看

由此可见,ComponentsIndex机制生效了。报错的原因很简单,因为扫描时只会读取上面的META-INF/spring.components文件,所以OrderServcie不是bean。
源码分析
下面我们简单阅读下源码:
org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#addCandidateComponentsFromIndex
private Set<BeanDefinition> addCandidateComponentsFromIndex(CandidateComponentsIndex index, String basePackage) {
Set<BeanDefinition> candidates = new LinkedHashSet<>();
try {
Set<String> types = new HashSet<>();
for (TypeFilter filter : this.includeFilters) {
String stereotype = extractStereotype(filter); // 这个地方默认就是@Component
if (stereotype == null) {
throw new IllegalArgumentException("Failed to extract stereotype from " + filter);
}
// 这里会看@ComponentScan扫描的包路径和注解
types.addAll(index.getCandidateTypes(basePackage, stereotype));
}
boolean traceEnabled = logger.isTraceEnabled();
boolean debugEnabled = logger.isDebugEnabled();
for (String type : types) {
MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(type);
// 依然会去调用isCandidateComponent方法,也就是黑名单,白名单,条件注解处理
if (isCandidateComponent(metadataReader)) {
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
sbd.setSource(metadataReader.getResource());
if (isCandidateComponent(sbd)) {
if (debugEnabled) {
logger.debug("Using candidate component class from index: " + type);
}
candidates.add(sbd);
}
else {
if (debugEnabled) {
logger.debug("Ignored because not a concrete top-level class: " + type);
}
}
}
else {
if (traceEnabled) {
logger.trace("Ignored because matching an exclude filter: " + type);
}
}
}
}
catch (IOException ex) {
throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
}
return candidates;
}经过分析可以发现,业务类依然是要标注@Component注解的,ComponentsIndex机制只是提供一个文件,扫描时直接读取这个文件里面的内容。值得一提的是,这个文件里面的内容可以定义不同包名的注解,也可以自定义注解。
这里要注意,多个@ComponentScan会多次从META-INF/spring.components文件中过滤。也就是说,如果没有ComponentsIndex机制时就要扫描包下所有的类一个个判断,而有了ComponentsIndex机制可以直接读取这个文件的内容。但是过滤出来的类,还是要经过includeFilters和excludeFilters以及@Conditional注解的匹配过滤!
总之,ComponentsIndex机制只是让扫描从META-INF/spring.components文件中过滤,业务类依然要加对应的注解,并不是说定义在文件中的就一定是Bean。