@ComponentScan 注解详解
@ComponentScan 注解详解
生效过程相关源码
@ComponentScan
的底层实现流程和之前我们分析 @Import实现原理 基本一致的,都是依靠配置类后置处理器 ConfigurationClassPostProcessor
进行处理、解析的,核心流程图如下所示:

所以我们这里直接看配置类解析器 ConfigurationClassParser
的解析方法 doProcessConfigurationClass()
protected final SourceClass doProcessConfigurationClass( ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter) throws IOException {
// 对@Component的解析处理,对@ComponentScan注解解析在下面,意味着会先跳过这里对@ComponentScan解析进行包扫描拿到生了@Component的beanDefinition,然后递归调用会再次来到这里解析@Component
if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
// Recursively process any member (nested) classes first
processMemberClasses(configClass, sourceClass, filter);
}
// 处理 @PropertySource 注解
for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable( sourceClass.getMetadata(), PropertySources.class, org.springframework.context.annotation.PropertySource.class)) {
if (this.environment instanceof ConfigurableEnvironment) {
processPropertySource(propertySource);
}
else {
logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() + "]. Reason: Environment must implement ConfigurableEnvironment");
}
}
// 处理@ComponentScan核心注解
// 这里是调用AnnotationConfigUtils的静态方法attributesForRepeatable,获取@ComponentScan注解的属性
Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
if (!componentScans.isEmpty() && !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
// for循环,遍历componentScans,此时仅有一个componentScan,使用componentScanParser解析器来解析componentScan这个对象
for (AnnotationAttributes componentScan : componentScans) {
// The config class is annotated with @ComponentScan -> perform the scan immediately
// componentScanParser解析器进行解析
Set<BeanDefinitionHolder> scannedBeanDefinitions = this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
// Check the set of scanned definitions for any further config classes and parse recursively if needed
// for循环扫描到的beanDefinition信息
for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
if (bdCand == null) {
bdCand = holder.getBeanDefinition();
}
// 这里递归调用前面的配置类解析器的解析方法,也就是会再次来到doProcessConfigurationClass()这个方法,会匹配到方法一开始的对@Component解析逻辑
if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
parse(bdCand.getBeanClassName(), holder.getBeanName());
}
}
}
}
// Process any @Import annotations
// 处理注解@import的入口方法
processImports(configClass, sourceClass, getImports(sourceClass), filter, true);
// Process any @ImportResource annotations
// 处理注解@ImportResource注解
AnnotationAttributes importResource = AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
if (importResource != null) {
String[] resources = importResource.getStringArray("locations");
Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
for (String resource : resources) {
String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
configClass.addImportedResource(resolvedResource, readerClass);
}
}
// 处理独立的,被 @Bean 注解修饰的方法
Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
for (MethodMetadata methodMetadata : beanMethods) {
configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
}
// Process default methods on interfaces
// 处理接口中的被@Bean 注解修饰的默认方法
processInterfaces(configClass, sourceClass);
// Process superclass, if any
// 处理父类方法
if (sourceClass.getMetadata().hasSuperClass()) {
String superclass = sourceClass.getMetadata().getSuperClassName();
if (superclass != null && !superclass.startsWith("java") && !this.knownSuperclasses.containsKey(superclass)) {
this.knownSuperclasses.put(superclass, configClass);
// Superclass found, return its annotation metadata and recurse
return sourceClass.getSuperClass();
}
}
// No superclass -> processing is complete
return null;
}
可以看到其中处理了众多注解:
-
@Component
-
@PropertySource
-
@ComponentScan
-
@Import
-
@ImportResource
-
@Bean
而且还会处理接口中的默认方法和当前类的父类中的相关注解
因为本篇文章我们重点是看 @ComponentScan
,因此我们重点看这一段儿:
// 处理@ComponentScan核心注解
// 这里是调用AnnotationConfigUtils的静态方法attributesForRepeatable,获取@ComponentScan注解的属性
Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
if (!componentScans.isEmpty() && !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
// for循环,遍历componentScans,此时仅有一个componentScan,使用componentScanParser解析器来解析componentScan这个对象
for (AnnotationAttributes componentScan : componentScans) {
// The config class is annotated with @ComponentScan -> perform the scan immediately
// componentScanParser解析器进行解析
Set<BeanDefinitionHolder> scannedBeanDefinitions = this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
// Check the set of scanned definitions for any further config classes and parse recursively if needed
// for循环扫描到的beanDefinition信息
for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
if (bdCand == null) {
bdCand = holder.getBeanDefinition();
}
// 这里递归调用前面的配置类解析器的解析方法,也就是会再次来到doProcessConfigurationClass()这个方法,会匹配到方法一开始的对@Component解析逻辑
if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
parse(bdCand.getBeanClassName(), holder.getBeanName());
}
}
}
}
重点是这一句:Set<BeanDefinitionHolder> scannedBeanDefinitions = this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
,我们看 ComponentScanAnnotationParser
的 parse()
// componentScan 包含着 @ComponentScan 注解的各个配置项的值
public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {
// 首先就初始化扫描器 ClassPathBeanDefinitionScanner
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry, componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);
// 接下来开始根据 @ComponentScan 注解的各个配置项的值来配置扫描器 scanner
// 处理配置项 nameGenerator
Class<? extends BeanNameGenerator> generatorClass = componentScan.getClass("nameGenerator");
boolean useInheritedGenerator = (BeanNameGenerator.class == generatorClass);
scanner.setBeanNameGenerator(useInheritedGenerator ? this.beanNameGenerator : BeanUtils.instantiateClass(generatorClass));
// 处理配置项 scopedProxy 和 scopeResolver
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));
}
// 使用配置项 resourcePattern
scanner.setResourcePattern(componentScan.getString("resourcePattern"));
// 使用配置项 includeFilters
for (AnnotationAttributes filter : componentScan.getAnnotationArray("includeFilters")) {
for (TypeFilter typeFilter : typeFiltersFor(filter)) {
scanner.addIncludeFilter(typeFilter);
}
}
// 使用配置项 excludeFilters
for (AnnotationAttributes filter : componentScan.getAnnotationArray("excludeFilters")) {
for (TypeFilter typeFilter : typeFiltersFor(filter)) {
scanner.addExcludeFilter(typeFilter);
}
}
// 使用配置项 lazyInit
boolean lazyInit = componentScan.getBoolean("lazyInit");
if (lazyInit) {
scanner.getBeanDefinitionDefaults().setLazyInit(true);
}
// 使用配置项 basePackages
// 这个配置很重要,这个配置直接决定了扫描器扫描的目标路径
// 这里并没有对注解配置的内容进行解析,而是只是进行了简单的处理
Set<String> basePackages = new LinkedHashSet<>();
String[] basePackagesArray = componentScan.getStringArray("basePackages");
for (String pkg : basePackagesArray) {
// 根据这个分隔符 ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS 从一个字符串中分割出多个路径,一般我们不这么用,
String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg), ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
Collections.addAll(basePackages, tokenized);
}
// 使用配置项 basePackageClasses
for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) {
// 其实还是获取 basePackageClasses指定的类的包路径字符串
basePackages.add(ClassUtils.getPackageName(clazz));
}
// 如果 @ComponentScan 未配置 basePackages basePackageClasses 这两个属性,则添加使用了 @ComponentScan 注解的类的包作为默认位置
if (basePackages.isEmpty()) {
basePackages.add(ClassUtils.getPackageName(declaringClass));
}
scanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false, false) {
@Override
protected boolean matchClassName(String className) {
return declaringClass.equals(className);
}
});
// 加载完 @ComponentScan 注解的配置,扫描器配置完毕,开始执行扫描basePackages basePackageClasses 决定的目标路径
return scanner.doScan(StringUtils.toStringArray(basePackages));
}
这个方法的工作很简单,初始化ClassPathBeanDefinitionScanner扫描器,根据 @ComponentScan
的属性,设置扫描器的属性,最后调用扫描器的 doScan()
方法执行真正的扫描工作。遍历扫描包,
我们接下来看 ClassPathBeanDefinitionScanner#doScan
,这个方法可出了大力气了
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) {
// 在指定的单个包路径下扫描bean的定义
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
for (BeanDefinition candidate : candidates) {
// 进行相关的Bean定义处理
// 确定 Scope,估计是 @Scope 注解相关的东西
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
candidate.setScope(scopeMetadata.getScopeName());
// 生成 bean 的名字,前面如果设置了自定义的bean名称生成器,这里就会作用
String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
if (candidate instanceof AbstractBeanDefinition) {
postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
}
if (candidate instanceof AnnotatedBeanDefinition) {
AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
}
// 然后检查BeanName是否冲突,
if (checkCandidate(beanName, candidate)) {
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
// 添加到beanDefinitions集合当中,
beanDefinitions.add(definitionHolder);
// 调用registerBeanDefinition注册Bean,将Bean的定义beanDefinition注册到Spring容器当中,方便后续注入bean。
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}
这个方法先是根据基础包路径来找到候选的 Bean,然后遍历扫描到的候选 Bean,给他们设置作用域,生成 BeanName 等一系列的操作。然后检查 BeanName 是否冲突,添加到 beanDefinitions 集合当中,调用 registerBeanDefinition
方法注册 Bean,将 Bean 的定义 beanDefinition 注册到 Spring 容器当中,方便后续注入 bean。
重点看看 findCandidateComponents()
,这个方法根据基础包路径来找到候选的 Bean。注意,这个方法在 ClassPathScanningCandidateComponentProvider
中
public Set<BeanDefinition> findCandidateComponents(String basePackage) {
// 这里没看懂
if (this.componentsIndex != null && indexSupportsIncludeFilters()) {
return addCandidateComponentsFromIndex(this.componentsIndex, basePackage);
}
else {
// 实际调用的方法
return scanCandidateComponents(basePackage);
}
}
实际调用的是 ClassPathScanningCandidateComponentProvider#scanCandidateComponents
这个方法
private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
Set<BeanDefinition> candidates = new LinkedHashSet<>();
try {
// ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX 为 classpath*:
// this.resourcePattern 也是一个固定字符串为 **/*.class (可被 @ComponentScan 注解的 resourcePattern 配置自定义)
// resolveBasePackage 方法本质上就是调用 ClassUtils.convertClassNameToResourcePath 方法,将报名中的 . 换成代表路径的 /
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + resolveBasePackage(basePackage) + '/' + this.resourcePattern;
// 然后在编译输出路径也就是target目录中到packageSearchPath这个路径下去扫描bean定义
Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
boolean traceEnabled = logger.isTraceEnabled();
boolean debugEnabled = logger.isDebugEnabled();
for (Resource resource : resources) {
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);
}
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;
}
就先分析到这里。
相关配置项
经过上面的源码分析,我们大概也就知道了 @ComponentScan
注解的所有配置项都是在 ComponentScanAnnotationParser#parse
这里生效的,现在我们来就像查看特定的某一个配置项的使用注意点。
basePackages
通过上面的源码分析,我们可以知道是在 ClassPathScanningCandidateComponentProvider#scanCandidateComponents
这个方法中将 basePackages
这个包路径字符串转化为实际的资源查找字符串。然后再通过 ResourcePatternResolver
来查找资源。
关于
ResourcePatternResolver
接口的解析,请看《Spring 中的资源获取》
basePackages 是可以包含通配符的,默认采用的是 ant 类型的通配符,在 ant 类型的通配符中,/**
可以匹配多层路径。
具体请看《Spring 中的资源获取》
这里我们重点看一下包路径中包含 *
类型通配符会有什么结果
-
包路径为:
xyz.xiashuo.basepackage
,对应的资源查找路径:classpath*:xyz/xiashuo/basepackag/**/*.class
,此时可以扫描到xyz.xiashuo.basepackage
包下的组件和其子包中的组件。 -
包路径为:
xyz.xiashuo.basepackage.*
,对应的资源查找路径:classpath*:xyz/xiashuo/basepackage/*/**/*.class
,此时从xyz.xiashuo.basepackage
的子包开始扫描,无法扫描就在xyz.xiashuo.basepackage
这个包下的组件。 -
包路径为:
xyz.xiashuo.basepackage.**
,对应的资源查找路径:classpath*:xyz/xiashuo/basepackage/**/**/*.class
,此时可以扫描到xyz.xiashuo.basepackage
包下的组件和其子包中的组件。 -
包路径为:
xyz.xiashuo.basepackage.*.target
,对应的资源查找路径:classpath*:xyz/xiashuo/basepackage/*/target/**/*.class
,此时从xyz.xiashuo.basepackage
的子包开始扫描,找到 target 包,并扫描 target 包下的组件,但是无法扫描到xyz.xiashuo.basepackage.target
包 -
包路径为:
xyz.xiashuo.basepackage.**.target
,对应的资源查找路径:classpath*:xyz/xiashuo/basepackage/**/target/**/*.class
,从xyz.xiashuo.basepackage
开始扫描,找到 target 包,并扫描 target 包下的组件,可以扫描到xyz.xiashuo.basepackage.target
包
我们后面在学习 Mybatis 的时候也可以通过 @MapperScan
注解扫描 Mapper 文件,其中也可以通过制定包路径,但是要注意,那里的包路径中的通配符跟这里的有所区别:
-
@MapperScan("com.demo.mapper")
:扫描指定包中的接口 -
@MapperScan("com.demo.*.mapper")
:一个*
代表一级包;比如可以扫到com.demo.aaa.mapper
,不能扫到com.demo.aaa.bbb.mapper
-
@MapperScan("com.demo.**.mapper")
:两个*
代表任意个包;比如可以扫到com.demo.aaa.mapper
,也可以扫到com.demo.aaa.bbb.mapper