@ComponentScan 注解详解

@ComponentScan 注解详解

生效过程相关源码

参考博客:Spring注解扫描: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;
}

可以看到其中处理了众多注解:

而且还会处理接口中的默认方法和当前类的父类中的相关注解

因为本篇文章我们重点是看 @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());,我们看 ComponentScanAnnotationParserparse()

// 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 中的资源获取》

这里我们重点看一下包路径中包含 * 类型通配符会有什么结果

我们后面在学习 Mybatis 的时候也可以通过 @MapperScan 注解扫描 Mapper 文件,其中也可以通过制定包路径,但是要注意,那里的包路径中的通配符跟这里的有所区别: