hexon
发布于 2025-12-17 / 5 阅读
0

5、扩展:ComponentsIndex机制的作用和底层实现

我在分析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。