SpringBoot啟動流程分析4:IoC容器的初始化過程

目錄

一、前言
二、obtainFreshBeanFactory();
三、prepareBeanFactory(beanFactory);
四、postProcessBeanFactory(beanFactory);
五、invokeBeanFactoryPostProcessors(beanFactory);
5.1、看看42-64行干了啥
5.1.1、findCandidateComponents(basePackage);
5.1.2、registerBeanDefinition(definitionHolder, this.registry);
5.2、@Import注解的解析過程

第五步:刷新應用上下文

一、前言

在前面的博客中談到IoC容器的初始化過程,主要分下面三步:

1 BeanDefinition的Resource定位
2 BeanDefinition的載入
3 向IoC容器注冊BeanDefinition

在上一篇文章介紹了prepareContext()方法,在準備刷新階段做了什么工作。本文我們主要從refresh()方法中總結IoC容器的初始化過程。

從run方法的,refreshContext()方法一路跟下去,最終來到AbstractApplicationContext類的refresh()方法。

@Override
public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        // Prepare this context for refreshing.
        //刷新上下文環(huán)境
        prepareRefresh();
        // Tell the subclass to refresh the internal bean factory.
        //這里是在子類中啟動 refreshBeanFactory() 的地方
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
        // Prepare the bean factory for use in this context.
        //準備bean工廠,以便在此上下文中使用
        prepareBeanFactory(beanFactory);
        try {
            // Allows post-processing of the bean factory in context subclasses.
            //設置 beanFactory 的后置處理
            postProcessBeanFactory(beanFactory);
            // Invoke factory processors registered as beans in the context.
            //調用 BeanFactory 的后處理器,這些處理器是在Bean 定義中向容器注冊的
            invokeBeanFactoryPostProcessors(beanFactory);
            // Register bean processors that intercept bean creation.
            //注冊Bean的后處理器,在Bean創(chuàng)建過程中調用
            registerBeanPostProcessors(beanFactory);
            // Initialize message source for this context.
            //對上下文中的消息源進行初始化
            initMessageSource();
            // Initialize event multicaster for this context.
            //初始化上下文中的事件機制
            initApplicationEventMulticaster();
            // Initialize other special beans in specific context subclasses.
            //初始化其他特殊的Bean
            onRefresh();
            // Check for listener beans and register them.
            //檢查監(jiān)聽Bean并且將這些監(jiān)聽Bean向容器注冊
            registerListeners();
            // Instantiate all remaining (non-lazy-init) singletons.
            //實例化所有的(non-lazy-init)單件
            finishBeanFactoryInitialization(beanFactory);
            // Last step: publish corresponding event.
            //發(fā)布容器事件,結束Refresh過程
            finishRefresh();
        } catch (BeansException ex) {
            if (logger.isWarnEnabled()) {
                logger.warn("Exception encountered during context initialization - " +
                        "cancelling refresh attempt: " + ex);
            }
            // Destroy already created singletons to avoid dangling resources.
            destroyBeans();
            // Reset 'active' flag.
            cancelRefresh(ex);
            // Propagate exception to caller.
            throw ex;
        } finally {
            // Reset common introspection caches in Spring's core, since we
            // might not ever need metadata for singleton beans anymore...
            resetCommonCaches();
        }
    }
}

從以上代碼中我們可以看到,refresh()方法中所作的工作也挺多,我們沒辦法面面俱到,主要根據IoC容器的初始化步驟和IoC依賴注入的過程進行分析,圍繞以上兩個過程,我們主要介紹重要的方法,其他的請看注釋。

二、obtainFreshBeanFactory();

在啟動流程的第三步:初始化應用上下文中我們創(chuàng)建了應用的上下文,并觸發(fā)了GenericApplicationContext類的構造方法如下所示,創(chuàng)建了beanFactory,也就是創(chuàng)建了DefaultListableBeanFactory類。

public GenericApplicationContext() {
     this.beanFactory = new DefaultListableBeanFactory();
}

關于obtainFreshBeanFactory()方法,其實就是拿到我們之前創(chuàng)建的beanFactory。

protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
    //刷新BeanFactory
    refreshBeanFactory();
    //獲取beanFactory
    ConfigurableListableBeanFactory beanFactory = getBeanFactory();
    if (logger.isDebugEnabled()) {
        logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
    }
    return beanFactory;
}

從上面代碼可知,在該方法中主要做了三個工作,刷新beanFactory,獲取beanFactory,返回beanFactory。

首先看一下refreshBeanFactory()方法,跟下去來到GenericApplicationContext類的refreshBeanFactory()發(fā)現也沒做什么。

@Override
protected final void refreshBeanFactory() throws IllegalStateException {
    if (!this.refreshed.compareAndSet(false, true)) {
        throw new IllegalStateException(
                "GenericApplicationContext does not support multiple refresh attempts: just call 'refresh' once");
    }
    this.beanFactory.setSerializationId(getId());
}

TIPS:

  1. AbstractApplicationContext類有兩個子類實現了refreshBeanFactory(),但是在前面第三步初始化上下文的時候,
    實例化了GenericApplicationContext類,所以沒有進入AbstractRefreshableApplicationContext中的refreshBeanFactory()方法。
  2. this.refreshed.compareAndSet(false, true)
    這行代碼在這里表示:GenericApplicationContext只允許刷新一次
    這行代碼,很重要,不是在Spring中很重要,而是這行代碼本身。首先看一下this.refreshed屬性:
    private final AtomicBoolean refreshed = new AtomicBoolean();
    java J.U.C并發(fā)包中很重要的一個原子類AtomicBoolean。通過該類的compareAndSet()方法可以實現一段代碼絕對只實現一次的功能。
    感興趣的自行百度吧。
image.png

三、prepareBeanFactory(beanFactory);

從字面意思上可以看出準備BeanFactory。

看代碼,具體看看做了哪些準備工作。這個方法不是重點,看注釋吧。

protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {
    // Tell the internal bean factory to use the context's class loader etc.
    // 配置類加載器:默認使用當前上下文的類加載器
    beanFactory.setBeanClassLoader(getClassLoader());
    // 配置EL表達式:在Bean初始化完成,填充屬性的時候會用到
    beanFactory.setBeanExpressionResolver(new StandardBeanExpressionResolver(beanFactory.getBeanClassLoader()));
    // 添加屬性編輯器 PropertyEditor
    beanFactory.addPropertyEditorRegistrar(new ResourceEditorRegistrar(this, getEnvironment()));

    // Configure the bean factory with context callbacks.
    // 添加Bean的后置處理器
    beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this));
    // 忽略裝配以下指定的類
    beanFactory.ignoreDependencyInterface(EnvironmentAware.class);
    beanFactory.ignoreDependencyInterface(EmbeddedValueResolverAware.class);
    beanFactory.ignoreDependencyInterface(ResourceLoaderAware.class);
    beanFactory.ignoreDependencyInterface(ApplicationEventPublisherAware.class);
    beanFactory.ignoreDependencyInterface(MessageSourceAware.class);
    beanFactory.ignoreDependencyInterface(ApplicationContextAware.class);

    // BeanFactory interface not registered as resolvable type in a plain factory.
    // MessageSource registered (and found for autowiring) as a bean.
    // 將以下類注冊到 beanFactory(DefaultListableBeanFactory) 的resolvableDependencies屬性中
    beanFactory.registerResolvableDependency(BeanFactory.class, beanFactory);
    beanFactory.registerResolvableDependency(ResourceLoader.class, this);
    beanFactory.registerResolvableDependency(ApplicationEventPublisher.class, this);
    beanFactory.registerResolvableDependency(ApplicationContext.class, this);

    // Register early post-processor for detecting inner beans as ApplicationListeners.
    // 將早期后處理器注冊為application監(jiān)聽器,用于檢測內部bean
    beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(this));

    // Detect a LoadTimeWeaver and prepare for weaving, if found.
    //如果當前BeanFactory包含loadTimeWeaver Bean,說明存在類加載期織入AspectJ,
    // 則把當前BeanFactory交給類加載期BeanPostProcessor實現類LoadTimeWeaverAwareProcessor來處理,
    // 從而實現類加載期織入AspectJ的目的。
    if (beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
        beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
        // Set a temporary ClassLoader for type matching.
        beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
    }

    // Register default environment beans.
    // 將當前環(huán)境變量(environment) 注冊為單例bean
    if (!beanFactory.containsLocalBean(ENVIRONMENT_BEAN_NAME)) {
        beanFactory.registerSingleton(ENVIRONMENT_BEAN_NAME, getEnvironment());
    }
    // 將當前系統(tǒng)配置(systemProperties) 注冊為單例Bean
    if (!beanFactory.containsLocalBean(SYSTEM_PROPERTIES_BEAN_NAME)) {
        beanFactory.registerSingleton(SYSTEM_PROPERTIES_BEAN_NAME, getEnvironment().getSystemProperties());
    }
    // 將當前系統(tǒng)環(huán)境 (systemEnvironment) 注冊為單例Bean
    if (!beanFactory.containsLocalBean(SYSTEM_ENVIRONMENT_BEAN_NAME)) {
        beanFactory.registerSingleton(SYSTEM_ENVIRONMENT_BEAN_NAME, getEnvironment().getSystemEnvironment());
    }
}

四、postProcessBeanFactory(beanFactory);

postProcessBeanFactory()方法向上下文中添加了一系列的Bean的后置處理器。后置處理器工作的時機是在所有的beanDenifition加載完成之后,bean實例化之前執(zhí)行。簡單來說Bean的后置處理器可以修改BeanDefinition的屬性信息。

關于這個方法就先這樣吧,有興趣的可以直接百度該方法。篇幅有限,對該方法不做過多介紹。

五、invokeBeanFactoryPostProcessors(beanFactory);(重點)

上面說過,IoC容器的初始化過程包括三個步驟,在invokeBeanFactoryPostProcessors()方法中完成了IoC容器初始化過程的三個步驟。

1,第一步:Resource定位

在SpringBoot中,我們都知道他的包掃描是從主類所在的包開始掃描的,prepareContext()方法中,會先將主類解析成BeanDefinition,然后在refresh()方法的invokeBeanFactoryPostProcessors()方法中解析主類的BeanDefinition獲取basePackage的路徑。這樣就完成了定位的過程。其次SpringBoot的各種starter是通過SPI擴展機制實現的自動裝配,SpringBoot的自動裝配同樣也是在invokeBeanFactoryPostProcessors()方法中實現的。還有一種情況,在SpringBoot中有很多的@EnableXXX注解,細心點進去看的應該就知道其底層是@Import注解,在invokeBeanFactoryPostProcessors()方法中也實現了對該注解指定的配置類的定位加載。

常規(guī)的在SpringBoot中有三種實現定位,第一個是主類所在包的,第二個是SPI擴展機制實現的自動裝配(比如各種starter),第三種就是@Import注解指定的類。(對于非常規(guī)的不說了)

2,第二步:BeanDefinition的載入

在第一步中說了三種Resource的定位情況,定位后緊接著就是BeanDefinition的分別載入。所謂的載入就是通過上面的定位得到的basePackage,SpringBoot會將該路徑拼接成:classpath:org/springframework/boot/demo//.class這樣的形式,然后一個叫做PathMatchingResourcePatternResolver的類會將該路徑下所有的.class文件都加載進來,然后遍歷判斷是不是有@Component注解,如果有的話,就是我們要裝載的BeanDefinition。大致過程就是這樣的了。
TIPS:
@Configuration,@Controller,@Service等注解底層都是@Component注解,只不過包裝了一層罷了。

3、第三個過程:注冊BeanDefinition

這個過程通過調用上文提到的BeanDefinitionRegister接口的實現來完成。這個注冊過程把載入過程中解析得到的BeanDefinition向IoC容器進行注冊。通過上文的分析,我們可以看到,在IoC容器中將BeanDefinition注入到一個ConcurrentHashMap中,IoC容器就是通過這個HashMap來持有這些BeanDefinition數據的。比如DefaultListableBeanFactory 中的beanDefinitionMap屬性。

OK,總結完了,接下來我們通過代碼看看具體是怎么實現的。

protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
    PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
    ...
}
// PostProcessorRegistrationDelegate類
public static void invokeBeanFactoryPostProcessors(
        ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {
    ...
    invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
    ...
}
// PostProcessorRegistrationDelegate類
private static void invokeBeanDefinitionRegistryPostProcessors(
        Collection<? extends BeanDefinitionRegistryPostProcessor> postProcessors, BeanDefinitionRegistry registry) {

    for (BeanDefinitionRegistryPostProcessor postProcessor : postProcessors) {
        postProcessor.postProcessBeanDefinitionRegistry(registry);
    }
}
// ConfigurationClassPostProcessor類
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    ...
    processConfigBeanDefinitions(registry);
}
// ConfigurationClassPostProcessor類
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
    ...
    do {
        parser.parse(candidates);
        parser.validate();
        ...
    }
    ...
}

一路跟蹤調用棧,來到ConfigurationClassParser類的parse()方法。

// ConfigurationClassParser類
public void parse(Set<BeanDefinitionHolder> configCandidates) {
    this.deferredImportSelectors = new LinkedList<>();
    for (BeanDefinitionHolder holder : configCandidates) {
        BeanDefinition bd = holder.getBeanDefinition();
        try {
            // 如果是SpringBoot項目進來的,bd其實就是前面主類封裝成的 AnnotatedGenericBeanDefinition(AnnotatedBeanDefinition接口的實現類)
            if (bd instanceof AnnotatedBeanDefinition) {
                parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
            } else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
                parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
            } else {
                parse(bd.getBeanClassName(), holder.getBeanName());
            }
        } catch (BeanDefinitionStoreException ex) {
            throw ex;
        } catch (Throwable ex) {
            throw new BeanDefinitionStoreException(
                    "Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
        }
    }
    // 加載默認的配置---》(對springboot項目來說這里就是自動裝配的入口了)
    processDeferredImportSelectors();
}

看上面的注釋,在前面的prepareContext()方法中,我們詳細介紹了我們的主類是如何一步步的封裝成AnnotatedGenericBeanDefinition,并注冊進IoC容器的beanDefinitionMap中的。

TIPS:
  至于processDeferredImportSelectors();方法,后面我們分析SpringBoot的自動裝配的時候會詳細講解,各種starter是如何一步步的實現自動裝配的。<SpringBoot啟動流程分析(五):SpringBoot自動裝配原理實現>

image

繼續(xù)沿著parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());方法跟下去

// ConfigurationClassParser類
protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
    processConfigurationClass(new ConfigurationClass(metadata, beanName));
}
// ConfigurationClassParser類
protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
    ...
    // Recursively process the configuration class and its superclass hierarchy.
    //遞歸地處理配置類及其父類層次結構。
    SourceClass sourceClass = asSourceClass(configClass);
    do {
        //遞歸處理Bean,如果有父類,遞歸處理,直到頂層父類
        sourceClass = doProcessConfigurationClass(configClass, sourceClass);
    }
    while (sourceClass != null);

    this.configurationClasses.put(configClass, configClass);
}
// ConfigurationClassParser類
protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
        throws IOException {

    // Recursively process any member (nested) classes first
    //首先遞歸處理內部類,(SpringBoot項目的主類一般沒有內部類)
    processMemberClasses(configClass, sourceClass);

    // Process any @PropertySource annotations
    // 針對 @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.warn("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
                    "]. Reason: Environment must implement ConfigurableEnvironment");
        }
    }

    // Process any @ComponentScan annotations
    // 根據 @ComponentScan 注解,掃描項目中的Bean(SpringBoot 啟動類上有該注解)
    Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
            sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
    if (!componentScans.isEmpty() &&
            !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
        for (AnnotationAttributes componentScan : componentScans) {
            // The config class is annotated with @ComponentScan -> perform the scan immediately
            // 立即執(zhí)行掃描,(SpringBoot項目為什么是從主類所在的包掃描,這就是關鍵了)
            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 (BeanDefinitionHolder holder : scannedBeanDefinitions) {
                BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
                if (bdCand == null) {
                    bdCand = holder.getBeanDefinition();
                }
                // 檢查是否是ConfigurationClass(是否有configuration/component兩個注解),如果是,遞歸查找該類相關聯的配置類。
                // 所謂相關的配置類,比如@Configuration中的@Bean定義的bean?;蛘咴谟蠤Component注解的類上繼續(xù)存在@Import注解。
                if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
                    parse(bdCand.getBeanClassName(), holder.getBeanName());
                }
            }
        }
    }

    // Process any @Import annotations
    //遞歸處理 @Import 注解(SpringBoot項目中經常用的各種@Enable*** 注解基本都是封裝的@Import)
    processImports(configClass, sourceClass, getImports(sourceClass), true);

    // Process any @ImportResource annotations
    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);
        }
    }

    // Process individual @Bean methods
    Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
    for (MethodMetadata methodMetadata : beanMethods) {
        configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
    }

    // Process default methods on interfaces
    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;
}

看doProcessConfigurationClass()方法。(SpringBoot的包掃描的入口方法,重點哦)

我們先大致說一下這個方法里面都干了什么,然后稍后再閱讀源碼分析。

TIPS:
  在以上代碼的第60行parse(bdCand.getBeanClassName(), holder.getBeanName());會進行遞歸調用,
因為當Spring掃描到需要加載的類會進一步判斷每一個類是否滿足是@Component/@Configuration注解的類,
如果滿足會遞歸調用parse()方法,查找其相關的類。
  同樣的第68行processImports(configClass, sourceClass, getImports(sourceClass), true);
通過@Import注解查找到的類同樣也會遞歸查找其相關的類。
  兩個遞歸在debug的時候會很亂,用文字敘述起來更讓人難以理解,所以,我們只關注對主類的解析,及其類的掃描過程。

上面代碼的第29行 for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(... 獲取主類上的@PropertySource注解(關于該注解是怎么用的請自行百度),解析該注解并將該注解指定的properties配置文件中的值存儲到Spring的 Environment中,Environment接口提供方法去讀取配置文件中的值,參數是properties文件中定義的key值。

42行 Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable( sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class); 解析主類上的@ComponentScan注解,呃,怎么說呢,42行后面的代碼將會解析該注解并進行包掃描。

68行 processImports(configClass, sourceClass, getImports(sourceClass), true); 解析主類上的@Import注解,并加載該注解指定的配置類。

TIPS:

在spring中好多注解都是一層一層封裝的,比如@EnableXXX,是對@Import注解的二次封裝。@SpringBootApplication注解=@ComponentScan+@EnableAutoConfiguration+@Import+@Configuration+@Component。@Controller,@Service等等是對@Component的二次封裝。。。

5.1、看看42-64行干了啥

從上面的42行往下看,來到第49行 Set<BeanDefinitionHolder> scannedBeanDefinitions = this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());

進入該方法

// ComponentScanAnnotationParser類
public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {
    ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,
            componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);
    ...
    // 根據 declaringClass (如果是SpringBoot項目,則參數為主類的全路徑名)
    if (basePackages.isEmpty()) {
        basePackages.add(ClassUtils.getPackageName(declaringClass));
    }
    ...
    // 根據basePackages掃描類
    return scanner.doScan(StringUtils.toStringArray(basePackages));
}

發(fā)現有兩行重要的代碼

為了驗證代碼中的注釋,debug,看一下declaringClass,如下圖所示確實是我們的主類的全路徑名。

image

跳過這一行,繼續(xù)debug,查看basePackages,該set集合中只有一個,就是主類所在的路徑。

TIPS:
  為什么只有一個還要用一個集合呢,因為我們也可以用@ComponentScan注解指定掃描路徑。

image

到這里呢IoC容器初始化三個步驟的第一步,Resource定位就完成了,成功定位到了主類所在的包。

接著往下看 return scanner.doScan(StringUtils.toStringArray(basePackages)); Spring是如何進行類掃描的。進入doScan()方法。

// ComponentScanAnnotationParser類
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) {
            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);
            }
            if (candidate instanceof AnnotatedBeanDefinition) {
                AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
            }
            if (checkCandidate(beanName, candidate)) {
                BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
                definitionHolder =
                        AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
                beanDefinitions.add(definitionHolder);
                //將該 Bean 注冊進 IoC容器(beanDefinitionMap)
                registerBeanDefinition(definitionHolder, this.registry);
            }
        }
    }
    return beanDefinitions;
}

這個方法中有兩個比較重要的方法,第7行 Set<BeanDefinition> candidates = findCandidateComponents(basePackage); 從basePackage中掃描類并解析成BeanDefinition,拿到所有符合條件的類后在第24行 registerBeanDefinition(definitionHolder, this.registry); 將該類注冊進IoC容器。也就是說在這個方法中完成了IoC容器初始化過程的第二三步,BeanDefinition的載入,和BeanDefinition的注冊。

5.1.1、findCandidateComponents(basePackage);

跟蹤調用棧

// ClassPathScanningCandidateComponentProvider類
public Set<BeanDefinition> findCandidateComponents(String basePackage) {
    ...
    else {
        return scanCandidateComponents(basePackage);
    }
}
// ClassPathScanningCandidateComponentProvider類
private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
    Set<BeanDefinition> candidates = new LinkedHashSet<>();
    try {
        //拼接掃描路徑,比如:classpath*:org/springframework/boot/demo/**/*.class
        String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
                resolveBasePackage(basePackage) + '/' + this.resourcePattern;
        //從 packageSearchPath 路徑中掃描所有的類
        Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
        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);
                    // //判斷該類是不是 @Component 注解標注的類,并且不是需要排除掉的類
                    if (isCandidateComponent(metadataReader)) {
                        //將該類封裝成 ScannedGenericBeanDefinition(BeanDefinition接口的實現類)類
                        ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
                        sbd.setResource(resource);
                        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 (Throwable ex) {
                    throw new BeanDefinitionStoreException(
                            "Failed to read candidate component class: " + resource, ex);
                }
            } else {
                if (traceEnabled) {
                    logger.trace("Ignored because not readable: " + resource);
                }
            }
        }
    } catch (IOException ex) {
        throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
    }
    return candidates;
}

在第13行將basePackage拼接成classpath:org/springframework/boot/demo//.class,在第16行的getResources(packageSearchPath);方法中掃描到了該路徑下的所有的類。然后遍歷這些Resources,在第27行判斷該類是不是 @Component 注解標注的類,并且不是需要排除掉的類。在第29行將掃描到的類,解析成ScannedGenericBeanDefinition,該類是BeanDefinition接口的實現類。OK,IoC容器的BeanDefinition載入到這里就結束了。

回到前面的doScan()方法,debug看一下結果(截圖中所示的就是我定位的需要交給Spring容器管理的類)。

5.1.2、registerBeanDefinition(definitionHolder, this.registry);

查看registerBeanDefinition()方法。是不是有點眼熟,在前面介紹prepareContext()方法時,我們詳細介紹了主類的BeanDefinition是怎么一步一步的注冊進DefaultListableBeanFactory的beanDefinitionMap中的。在此呢我們就省略1w字吧。完成了BeanDefinition的注冊,就完成了IoC容器的初始化過程。此時,在使用的IoC容器DefaultListableFactory中已經建立了整個Bean的配置信息,而這些BeanDefinition已經可以被容器使用了。他們都在BeanbefinitionMap里被檢索和使用。容器的作用就是對這些信息進行處理和維護。這些信息是容器依賴反轉的基礎。

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

OK,到這里IoC容器的初始化過程的三個步驟就梳理完了。當然這只是針對SpringBoot的包掃描的定位方式的BeanDefinition的定位,加載,和注冊過程。前面我們說過,還有兩種方式@Import和SPI擴展實現的starter的自動裝配。

5.2、@Import注解的解析過程

相信不說大家也應該知道了,各種@EnableXXX注解,很大一部分都是對@Import的二次封裝(其實也是為了解耦,比如當@Import導入的類發(fā)生變化時,我們的業(yè)務系統(tǒng)也不需要改任何代碼)。

呃,我們又要回到上文中的ConfigurationClassParser類的doProcessConfigurationClass方法的第68行processImports(configClass, sourceClass, getImports(sourceClass), true);,跳躍性比較大。上面解釋過,我們只針對主類進行分析,因為這里有遞歸。

processImports(configClass, sourceClass, getImports(sourceClass), true);中configClass和sourceClass參數都是主類相對應的哦。

TIPS:
  在分析這一塊的時候,我在主類上加了@EnableCaching注解。

image

首先看getImports(sourceClass);

private Set<SourceClass> getImports(SourceClass sourceClass) throws IOException { 
     Set<SourceClass> imports = new LinkedHashSet<>(); 
     Set<SourceClass> visited = new LinkedHashSet<>(); 
     collectImports(sourceClass, imports, visited); 
     return imports; 
 }

debug

image

正是@EnableCaching注解中的@Import注解指定的類。另外兩個呢是主類上的@SpringBootApplication中的@Import注解指定的類。不信你可以一層層的剝開@SpringBootApplication注解的皮去一探究竟。

至于processImports()方法,大家自行debug吧,相信看到這里,思路大家都已經很清楚了。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容