springboot啓動流程(八)ioc容器refresh過程(下篇)

全部文章

http://www.javashuo.com/article/p-uvudtich-bm.htmlhtml

 

正文

上一篇文章,咱們知道了解析過程將從解析main方法所在的主類開始。在文章的最後咱們稍微看了一下ConfigurationClassParser這個解析器的parse方法java

protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
    processConfigurationClass(new ConfigurationClass(metadata, beanName));
}

本文將從這個parse方法繼續下去,看看解析main方法所在的主類這個過程主要發生了什麼。spring

 

跟進processConfigurationClass方法springboot

protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
    // 

    // 由main方法所在的主類開始,向超類逐層向上遞歸解析
    SourceClass sourceClass = asSourceClass(configClass);
    do {
        // 這裏包含了解析單個配置類的核心邏輯
        sourceClass = doProcessConfigurationClass(configClass, sourceClass);
    } while (sourceClass != null);

    // 
}

咱們注意到,doProcessConfigurationClass方法將會完成解析的主要工做,可是又會返回一個新的sourceClass用於解析。而這個新的sourceClass會是當前上一個sourceClass的父類。所在解析過程是一個遞歸過程,由主類開始,向超類逐層向上遞歸解析處理。app

 

繼續跟進doProcessConfigurationClass方法,咱們看看這個核心的解析邏輯。代碼量對較多,咱們只關注兩個點this

1)@ComponentScan註解解析,掃描並註冊BeanDefinitionlua

2)獲取超類向上遞歸spa

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

    // 處理@ComponentScan註解
    Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
    if (!componentScans.isEmpty() &&
            !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
        // 遍歷@ComponentScan的屬性值
        for (AnnotationAttributes componentScan : componentScans) {
            // 解析掃描
            Set<BeanDefinitionHolder> scannedBeanDefinitions = this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
            // 
        }
    }

    // 

    // 判斷是否有超類
    if (sourceClass.getMetadata().hasSuperClass()) {
        String superclass = sourceClass.getMetadata().getSuperClassName();
        if (superclass != null && !superclass.startsWith("java") &&
                !this.knownSuperclasses.containsKey(superclass)) {
            this.knownSuperclasses.put(superclass, configClass);
            // 返回待解析的超類
            return sourceClass.getSuperClass();
        }
    }

    // 沒有超類,則解析完畢
    return null;
}

首先咱們main方法所在的主類是被@SpringbootApplication註解所標註的,而@SpringbootApplication組合了@ComponentScan。所謂解析主類的時候將會處理@ComponentScan註解。解析@ComponentScan的主要工做的實現由ComponentScanAnnotationParser這個解析器來完成。一般這個解析器完成以後,被掃描到的BeanDefinition將會被註冊到BeanFactory當中。code

doProcessConfigurationClass方法的最後一部分是從當前被解析的類元數據中獲取超類,若是超類存在且須要被解析那麼就當作返回值返回回去,從而被外層的方法給遞歸處理。component

 

@ComponentScan註解解析

下面,咱們跟進ComponentScanAnnotationParser這個解析器的parse方法,看看@ComponentScan的處理過程

public Set<BeanDefinitionHolder> parse(
    AnnotationAttributes componentScan, 
    final String declaringClass) {
    ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry, componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);

    //

    Set<String> basePackages = new LinkedHashSet<>();
    // 從basePackages配置獲取掃描路徑
    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);
    }
    // 從basePackageClasses獲取掃描路徑
    for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) {
        basePackages.add(ClassUtils.getPackageName(clazz));
    }
    if (basePackages.isEmpty()) {
        // 默認添加當前被解析類的路徑做爲根路徑
        basePackages.add(ClassUtils.getPackageName(declaringClass));
    }

    // 

    // 掃描目標路徑
    return scanner.doScan(StringUtils.toStringArray(basePackages));
}    

這裏獲取了一個掃描器,而後找到了待掃描的路徑,最後利用掃描器去掃描路徑。

 

跟進doScan方法

protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
    
    Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
    for (String basePackage : basePackages) {
        // 掃描獲取BeanDefinition
        Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
        for (BeanDefinition candidate : candidates) {
            //

            if (checkCandidate(beanName, candidate)) {
                BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
                definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
                beanDefinitions.add(definitionHolder);
                // 註冊BeanDefinition到BeanFactory
                registerBeanDefinition(definitionHolder, this.registry);
            }
        }
    }
    return beanDefinitions;
}

咱們看到,findCandidateComponents方法將會根據掃描路徑獲取BeanDefinition,而掃描出來的BeanDefinition將會進入註冊方法registerBeanDefinition。

 

咱們先跟進findCandidateComponents方法看看如何掃描獲取

public Set<BeanDefinition> findCandidateComponents(String basePackage) {
    if (this.componentsIndex != null && indexSupportsIncludeFilters()) {
        return addCandidateComponentsFromIndex(this.componentsIndex, basePackage);
    } else {
        // 進入
        return scanCandidateComponents(basePackage);
    }
}

再跟進scanCandidateComponents方法

private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
    Set<BeanDefinition> candidates = new LinkedHashSet<>();
    try {
        // 拼接出搜索路徑,例如:classpath*:cn/lay/springbootlearn/**/*.class
        String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + resolveBasePackage(basePackage) + '/' + this.resourcePattern;
        // 獲取搜索路徑下待處理資源
        Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
        // 
        for (Resource resource : resources) {
            // 
            if (resource.isReadable()) {
                try {
                    MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
                    if (isCandidateComponent(metadataReader)) {
                        // 轉化成BeanDefinition
                        ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
                        // 
                    } else {
                        // 
                    }
                } catch (Throwable ex) {
                    // 
                }
            } else {
                // 
            }
        }
    } catch (IOException ex) {
        // 
    }
    return candidates;
}

basePackage將會被拼接成搜索路徑,如:classpath*:cn/lay/springbootlearn/**/*.class。而getResources方法將會從搜索路徑中獲取相應的資源對象,這些資源對象並最終被讀取並轉化爲BeanDefinition。

到這裏,@ComponentScan掃描的Bean就已經成爲了BeanDefinition,可是還有一步就是將BeanDefinition註冊到BeanFactory當中。

 

咱們回到doScan方法,並跟進registerBeanDefinition方法,看看註冊過程

protected void registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) {
    BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, registry);
}

繼續跟進registerBeanDefinition

public static void registerBeanDefinition(
        BeanDefinitionHolder definitionHolder, 
        BeanDefinitionRegistry registry) throws BeanDefinitionStoreException {

    String beanName = definitionHolder.getBeanName();
    registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());

    // 省略
}

咱們看到這裏直接註冊到了BeanDefinitionRegistry中去了,其實就是註冊到BeanFactory當中。BeanFactory的默認實現類DefaultListableBeanFactory實現了BeanDefinitionRegistry,因此DefaultListableBeanFactory便是BeanDefinition的註冊位置。

 

跟進DefaultListableBeanFactory的registerBeanDefinition方法

private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);

public void registerBeanDefinition(
        String beanName, 
        BeanDefinition beanDefinition) throws BeanDefinitionStoreException {

    // 
    if (existingDefinition != null) {
        //
    } else {
        if (hasBeanCreationStarted()) {
            // Cannot modify startup-time collection elements anymore (for stable iteration)
            synchronized (this.beanDefinitionMap) {
                this.beanDefinitionMap.put(beanName, beanDefinition);
                // 
            }
        } else {
            //
        }
        //
    }
    // 
}

最終,也就是將BeanDefinition添加到一個key-value的集合當中,這樣就完成了註冊工做。

 

總結

到這裏,ioc容器refresh過程部分就結束了。咱們略過很多東西,將解析主類、解析@ComponentScan掃描Bean定義、註冊到BeanFactory這個主要的流程過了一遍。固然,在這裏可能還存在一個比較困惑的點。前面的文章中,咱們提過幾回:配置 -> BeanDefinition -> Bean這樣一個過程。ioc的refresh過程卻只有從配置 -> BeanDefinition這樣一個過程,那麼BeanDefinition -> Bean這個過程又在哪裏呢?後面ioc容器注入部分將說明這部份內容。

相關文章
相關標籤/搜索