SpringBoot源碼解析 -- @ComponentScan的實現原理

上一篇解析SpringBoot AutoConfigure功能的文章說過,ConfigurationClassParser#doProcessConfigurationClass方法很重要,處理@Component,@PropertySources,@ComponentScans,@Import,@ImportResource等註解。
如今來看一下@ComponentScans註解的處理。
源碼分析基於spring boot 2.1spring

ConfigurationClassParser#doProcessConfigurationClass微信

protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
        throws IOException {

    ...

    for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
            sourceClass.getMetadata(), PropertySources.class,
            org.springframework.context.annotation.PropertySource.class)) {    // #1
        if (this.environment instanceof ConfigurableEnvironment) {
            processPropertySource(propertySource);
        }
        ...
    }

    Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
            sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);    // #2
    if (!componentScans.isEmpty() &&
            !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {    // #3
        for (AnnotationAttributes componentScan : componentScans) {
            // The config class is annotated with @ComponentScan -> perform the scan immediately
            Set<BeanDefinitionHolder> scannedBeanDefinitions =
                    this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());    // #4
            // Check the set of scanned definitions for any further config classes and parse recursively if needed
            for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
                BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
                if (bdCand == null) {
                    bdCand = holder.getBeanDefinition();
                }
                if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {    // #5
                    parse(bdCand.getBeanClassName(), holder.getBeanName());
                }
            }
        }
    }

    ...
}

#1 處理@PropertySources註解,獲取對應的PropertySources屬性源,添加到Environment中
關於PropertySources與Environment的關係,後面會寫文章解析。
#2 獲取SourceClass上的ComponentScans配置
#3 若是存在@Conditional註解,取註解中Condition條件判斷類進行判斷
#4 使用ComponentScanAnnotationParser處理ComponentScan,掃描指定目錄下的bean
#5 檢查掃描出來的bean是否還有ConfigurationClass,若是有,遞歸處理app

ComponentScanAnnotationParser#parse -> ClassPathBeanDefinitionScanner#doScanide

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) {
            Set<BeanDefinition> candidates = findCandidateComponents(basePackage);    // #1
            for (BeanDefinition candidate : candidates) {
                ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
                candidate.setScope(scopeMetadata.getScopeName());
                String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
                if (candidate instanceof AbstractBeanDefinition) {    
                    postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);    // #2
                }
                if (candidate instanceof AnnotatedBeanDefinition) {    
                    AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);    // #3
                }
                if (checkCandidate(beanName, candidate)) {    
                    BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
                    definitionHolder =
                            AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
                    beanDefinitions.add(definitionHolder);
                    registerBeanDefinition(definitionHolder, this.registry);    // #4
                }
            }
        }
        return beanDefinitions;
    }

#1 掃描路徑,獲取候選類
#2 給bean設置默認的配置,如LazyInit,AutowireMode,InitMethodName
#3 從Class獲取註解配置信息(如@Lazy,@DependsOn),設置到BeanDefinition,
#4 將掃描到的BeanDefinition註冊到spring中源碼分析

ClassPathBeanDefinitionScanner#findCandidateComponents -> ClassPathScanningCandidateComponentProvider#scanCandidateComponentspost

private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
    Set<BeanDefinition> candidates = new LinkedHashSet<>();
    try {
        String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
                resolveBasePackage(basePackage) + '/' + this.resourcePattern;
        Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);    // #1
        boolean traceEnabled = logger.isTraceEnabled();
        boolean debugEnabled = logger.isDebugEnabled();
        for (Resource resource : resources) {
            if (traceEnabled) {
                logger.trace("Scanning " + resource);
            }
            if (resource.isReadable()) {
                try {
                    MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);        // #2
                    if (isCandidateComponent(metadataReader)) {        // #3
                        ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);    // #4
                        sbd.setResource(resource);
                        sbd.setSource(resource);
                        if (isCandidateComponent(sbd)) {    // #5
                            if (debugEnabled) {
                                logger.debug("Identified candidate component class: " + resource);
                            }
                            candidates.add(sbd);    // #6
                        }
                        ...
}

#1 掃描給定目錄及子目錄下全部的class文件
#2 生成SimpleMetadataReader,使用ASM讀取class文件
#3 判斷掃描到的BeanDefinition是否知足注入條件
#4 生成ScannedGenericBeanDefinition,該BeanDefinition實現了AnnotatedBeanDefinition接口,使用ASM(複用SimpleMetadataReader)獲取Class的註解信息,而不須要JVM加載class
AnnotatedBeanDefinition對BeanDefinition擴展,能夠獲取Class的註解信息。
AnnotationMetadata表示Class註解的元數據,標準實現類爲StandardAnnotationMetadata,而AnnotationMetadataReadingVisitor使用訪問者模式,經過ASM獲取註解信息。
#5 檢查BeanDefinition是否爲非接口,非循環依賴
#6 保存結果this

ClassPathScanningCandidateComponentProvider#isCandidateComponentlua

protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
        for (TypeFilter tf : this.excludeFilters) {    
            if (tf.match(metadataReader, getMetadataReaderFactory())) {    // #1
                return false;
            }
        }
        for (TypeFilter tf : this.includeFilters) {
            if (tf.match(metadataReader, getMetadataReaderFactory())) {    // #2
                return isConditionMatch(metadataReader);
            }
        }
        return false;
    }

#1 使用excludeFilters過濾BeanDefinition
#2 使用includeFilters篩選BeanDefinitionspa

ClassPathScanningCandidateComponentProvider#registerDefaultFilters方法,會給includeFilter添加默認的AnnotationTypeFilter,負責處理@Component,@ManagedBean等註解。debug

AnnotationTypeFilter#match -> matchSelf

protected boolean matchSelf(MetadataReader metadataReader) {
    AnnotationMetadata metadata = metadataReader.getAnnotationMetadata();    // #1
    return metadata.hasAnnotation(this.annotationType.getName()) ||    // #2
            (this.considerMetaAnnotations && metadata.hasMetaAnnotation(this.annotationType.getName()));    // #3
}

#1 獲取Class的註解元數據
#2 檢查Class上是否有對應的annotationType
#3 檢查Class的嵌套註解是否有對應的annotationType

@Service,@Repository,@Controller註解上都標註了@Component註解,若是Class上使用了這些註解,#3步驟是返回true的

到這裏,@ComponentScans註解掃描標註了@Component的Bea的實現原理就說完了。
簡單來講,Spring掃描對應目錄下的class,生成BeanDefinition並註冊到Spring上下文。最後構造bean的操做,是在AbstractApplicationContext#refresh方法中,調用finishBeanFactoryInitialization,構建熱加載的單例bean時完成。

若是您以爲本文不錯,歡迎關注個人微信公衆號,您的關注是我堅持的動力!

相關文章
相關標籤/搜索