在 Spring 框架中,扫描机制是其核心功能之一,它负责在应用程序启动时自动发现和注册相关的组件。本文将深入探讨 Spring 的扫描原理,包括扫描过程以及相关源码的分析。
spring提供了两种扫描方式来进行扫描:1.我们可以通过AnnotationConfigApplicationContext的scan方法中进行设置固定的包名路径;2.我们可以通过@ComponentScan注解来进行配置要进行扫描的包路径。这两种方式底层都是进行执行的是ClassPathBeanDefinitionScanner 类来进行扫描的操作。这两种方式的主要区别在于执行的时机不同,本文介绍**@ComponentScan**注解来进行配置的扫描方式
1.spring的扫描时机
spring 容器初始化的时候,会进行执行invokeBeanFactoryPostProcessors方法,内部会进行调用ConfigurationClassPostProcessor的postProcessBeanDefinitionRegistry方法的后完成了bean的扫描操作。
2.spring 扫描流程
通过我们对代码的分析,我们发现spring进行扫描的入口为org.springframework.context.annotation.ComponentScanAnnotationParser#parse。
java
public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, String declaringClass) {
//扫描器的初始化操作
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,
componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);
//获取名字生成器的策略 (针对的是bean的名字)
Class<? extends BeanNameGenerator> generatorClass = componentScan.getClass("nameGenerator");
boolean useInheritedGenerator = (BeanNameGenerator.class == generatorClass);
scanner.setBeanNameGenerator(useInheritedGenerator ? this.beanNameGenerator :
BeanUtils.instantiateClass(generatorClass));
ScopedProxyMode scopedProxyMode = componentScan.getEnum("scopedProxy");
if (scopedProxyMode != ScopedProxyMode.DEFAULT) {
scanner.setScopedProxyMode(scopedProxyMode);
}
else {
Class<? extends ScopeMetadataResolver> resolverClass = componentScan.getClass("scopeResolver");
scanner.setScopeMetadataResolver(BeanUtils.instantiateClass(resolverClass));
}
//获取扫描的包路径
scanner.setResourcePattern(componentScan.getString("resourcePattern"));
//可以自己添加一些include的过滤器
//mybatis 框架对此有一些自己的扩展
for (AnnotationAttributes filter : componentScan.getAnnotationArray("includeFilters")) {
for (TypeFilter typeFilter : typeFiltersFor(filter)) {
scanner.addIncludeFilter(typeFilter);
}
}
// 添加排除扫描的过滤器
for (AnnotationAttributes filter : componentScan.getAnnotationArray("excludeFilters")) {
for (TypeFilter typeFilter : typeFiltersFor(filter)) {
scanner.addExcludeFilter(typeFilter);
}
}
boolean lazyInit = componentScan.getBoolean("lazyInit");
if (lazyInit) {
scanner.getBeanDefinitionDefaults().setLazyInit(true);
}
// 扫描的包路径的配置
Set<String> basePackages = new LinkedHashSet<>();
String[] basePackagesArray = componentScan.getStringArray("basePackages");
for (String pkg : basePackagesArray) {
String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg),
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
Collections.addAll(basePackages, tokenized);
}
for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) {
basePackages.add(ClassUtils.getPackageName(clazz));
}
if (basePackages.isEmpty()) {
basePackages.add(ClassUtils.getPackageName(declaringClass));
}
scanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false, false) {
@Override
protected boolean matchClassName(String className) {
return declaringClass.equals(className);
}
});
// 开始进行扫描
return scanner.doScan(StringUtils.toStringArray(basePackages));
}
2.1 扫描器的初始化操作
通过源码,我们可以看到spring的扫描器初始化的时候默认进行注册了三个include过滤器。
java
public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters,
Environment environment, @Nullable ResourceLoader resourceLoader) {
Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
this.registry = registry;
// 针对@CompentScan注解这个值默认为true
if (useDefaultFilters) {
registerDefaultFilters();
}
setEnvironment(environment);
setResourceLoader(resourceLoader);
}
进行构建ClassPathBeanDefinitionScanner类对象的时候会进行注册三个默认的include过滤器。从代码中我们可以看出这三种过滤器都是AnnotationTypeFilter类型。这三种过滤器分别是针对@Component,@ManagedBean,@Named三种注解进行扫描操作。这块有一个小问题为啥@Component注解和@ManagedBean,@Named的写法不一致呢,有大神知道的话,可以告诉我一下
java
@SuppressWarnings("unchecked")
protected void registerDefaultFilters() {
this.includeFilters.add(new AnnotationTypeFilter(Component.class));
ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader();
try {
this.includeFilters.add(new AnnotationTypeFilter(
((Class<? extends Annotation>) ClassUtils.forName("javax.annotation.ManagedBean", cl)), false));
logger.trace("JSR-250 'javax.annotation.ManagedBean' found and supported for component scanning");
}
catch (ClassNotFoundException ex) {
// JSR-250 1.1 API (as included in Java EE 6) not available - simply skip.
}
try {
this.includeFilters.add(new AnnotationTypeFilter(
((Class<? extends Annotation>) ClassUtils.forName("javax.inject.Named", cl)), false));
logger.trace("JSR-330 'javax.inject.Named' annotation found and supported for component scanning");
}
catch (ClassNotFoundException ex) {
// JSR-330 API not available - simply skip.
}
}
2.2 根据包名进行解析
解析@CompentScan注解得到包名, 然后根据包名扫描包下所有的文件, 遍历这些文件,通过ASM字节码技术读取这些文件信息,封装成一个metadataReader对象。调用isCandidateComponent方法,进行判断是否排除,是否添加。如果通过include的filter 则进行实例化成ScannedGenericBeanDefinition对象。
java
private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
Set<BeanDefinition> candidates = new LinkedHashSet<>();
try {
//拼接扫描包的路径
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
resolveBasePackage(basePackage) + '/' + this.resourcePattern;
//会将该包路径下的文件解析成一个Resource对象
Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
boolean traceEnabled = logger.isTraceEnabled();
boolean debugEnabled = logger.isDebugEnabled();
for (Resource resource : resources) {
if (traceEnabled) {
logger.trace("Scanning " + resource);
}
try {
//将resource对象 封装成metaDataReader
MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
// includeFilter和excludeFilter的双重过滤
if (isCandidateComponent(metadataReader)) {
//将metadataReader对象封装成一个ScannedGenericBeanDefinition 对象
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
sbd.setSource(resource);
// 进行判断ScannedGenericBeanDefinition 对象是否是接口
// 是否是抽象类,是否加了LookUp注解等
if (isCandidateComponent(sbd)) {
if (debugEnabled) {
logger.debug("Identified candidate component class: " + resource);
}
candidates.add(sbd);
}
else {
if (debugEnabled) {
logger.debug("Ignored because not a concrete top-level class: " + resource);
}
}
}
else {
if (traceEnabled) {
logger.trace("Ignored because not matching any filter: " + resource);
}
}
}
catch (FileNotFoundException ex) {
if (traceEnabled) {
logger.trace("Ignored non-readable " + resource + ": " + ex.getMessage());
}
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to read candidate component class: " + resource, ex);
}
}
}
catch (IOException ex) {
throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
}
return candidates;
}