深入理解Spring容器初始化(一):上下文的初始化

前言

我們知道,spring 的啓動其實就是容器的啓動,而一般情況下,容器指的其實就是上下文 ApplicationContext

AbstractApplicationContext 作爲整個 ApplicationContext 體系中最高級的抽象類,爲除了 ComplexWebApplicationContextSimpleWebApplicationContext 這兩個容器外的全部容器,規定好了 refresh 的整體流程,所有的容器在完成一些自己的初始化配置後,都需要調用該 refresh 方法,依次完成指定內容的初始化。

也就是說,讀懂了 AbstractApplicationContext.refresh() 方法,其實就讀懂了容器的啓動流程:

public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {

        // ================= 一、上下文的初始化 =================
        // 準備上下文
        prepareRefresh();
        // 通知子類刷新內部工廠
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
        // 準備bean工廠以便在當前上下文中使用
        prepareBeanFactory(beanFactory);

        try {
            // ================= 二、BeanFactory的初始化 =================
            // 對工廠進行默認後置處理
            postProcessBeanFactory(beanFactory);
            // 使用後置處理器對工廠進行處理
            invokeBeanFactoryPostProcessors(beanFactory);
            // 註冊Bean後置處理器
            registerBeanPostProcessors(beanFactory);

            // ================= 三、事件,Bean及其他配置的初始化 =================
            // 初始化此上下文的消息源
            initMessageSource();
            // 爲此上下文初始化事件廣播者
            initApplicationEventMulticaster();
            // 初始化特定上下文子類中的其他特殊bean
            onRefresh();
            // 檢查偵聽器bean並註冊
            registerListeners();
            // 實例化所有非懶加載的剩餘單例
            finishBeanFactoryInitialization(beanFactory);
            // 完成刷新
            finishRefresh();
        }


        // ================= 異常處理 =================
        catch (BeansException ex) {
            if (logger.isWarnEnabled()) {
                logger.warn("Exception encountered during context initialization - " +
                            "cancelling refresh attempt: " + ex);
            }
            // 銷燬已創建的單例
            destroyBeans();
            // 重置上下文的激活狀態
            cancelRefresh(ex);
            throw ex;
        }
        finally {
            // 重置內部的一些元數據緩存
            resetCommonCaches();
        }
    }
}

從總體來看,該方法描述的初始化過程大概分爲三步:

筆者將基於 spring 源碼 5.2.x 分支,分別通過五篇文章從源碼分析 spring 容器的初始化過程。

本文是其中的第一篇文章,將介紹上下文的初始化過程。

相關文章:

一、刷新上下文屬性

上下文的初始化,對應 prepareRefreshobtainFreshBeanFactoryprepareBeanFactory 三個步驟。

其中,調用 prepareRefresh 是第一個步驟,它用於用於初始化 ApplicationContext 本身的一些屬性。

protected void prepareRefresh() {
    // 設置啓動狀態,並輸出日誌
    this.startupDate = System.currentTimeMillis();
    this.closed.set(false);
    this.active.set(true);

    if (logger.isDebugEnabled()) {
        if (logger.isTraceEnabled()) {
            logger.trace("Refreshing " + this);
        }
        else {
            logger.debug("Refreshing " + getDisplayName());
        }
    }

    // 初始化上下文環境中的屬性數據源佔位符
    initPropertySources();
    
    // 校驗必要的屬性參數
    getEnvironment().validateRequiredProperties();

    // 刷新早期事件監聽器
    if (this.earlyApplicationListeners == null) {
        this.earlyApplicationListeners = new LinkedHashSet<>(this.applicationListeners);
    }
    else {
        this.applicationListeners.clear();
        this.applicationListeners.addAll(this.earlyApplicationListeners);
    }
    // 允許使用的早期事件,在廣播器完成初始化後可以發佈
    this.earlyApplicationEvents = new LinkedHashSet<>();
}

1、初始化屬性數據源

initPropertySources 方法在 AbstractApplicationContext 是個空方法:

protected void initPropertySources() {
    // For subclasses: do nothing by default.
}

這裏需要先強調一下佔位符的概念。由於在 spring 啓動過程,很多對象的需要經過多個階段才能徹底完成配置,但是在未加載完畢的時候就需要在其他地方被引用,爲此 spring 中很多地方引入了名稱爲 XXXHolder 的類,用 Holder 表示一個還未加載完成的實例。

initPropertySources 的本意是將一些上下文屬性裏面數據源的佔位符替換爲真正的資源對象,實際場景中,該方法只有在 WebApplicationContext 體系下的實現類會去重寫該方法,比如 AbstractRefreshableWebApplicationContext

@Override
protected void initPropertySources() {
    ConfigurableEnvironment env = getEnvironment();
    if (env instanceof ConfigurableWebEnvironment) {
        ((ConfigurableWebEnvironment) env).initPropertySources(this.servletContext, this.servletConfig);
    }
}

這個方法的作用是在 web 環境中,把 servlet 相關的一些配置的佔位符替換爲真正的 servletContextservletConfig

2、必要屬性參數的校驗

getEnvironment().validateRequiredProperties() 這行代碼的作用的校驗必要的屬性參數,實際操作的是 Environment,我們以 AbstractEnvironment#validateRequiredProperties 爲例,該方法最後會調用 AbstractPropertyResolver#validateRequiredProperties

// AbstractEnvironment
@Override
public void validateRequiredProperties() throws MissingRequiredPropertiesException {
    this.propertyResolver.validateRequiredProperties();
}

// AbstractPropertyResolver
@Override
public void validateRequiredProperties() {
    MissingRequiredPropertiesException ex = new MissingRequiredPropertiesException();
    for (String key : this.requiredProperties) {
        if (this.getProperty(key) == null) {
            ex.addMissingRequiredProperty(key);
        }
    }
    if (!ex.getMissingRequiredProperties().isEmpty()) {
        throw ex;
    }
}

這裏的處理非常簡單,遍歷非空屬性,如果是空就拋出 MissingRequiredPropertiesException 異常。

二、刷新上下文中的工廠

調用 AbstarctApplicationContext.obtainFreshBeanFactory() 方法是初始化容器的第二步。

該方法主要用於關閉上下文中可能存在的舊 BeanFactory,並創建新的 BeanFactory

AbstractApplicationContext 中對 refreshBeanFactory 的解釋,該方法的實現按容器是否可重複刷新分爲兩種:

  • 直接返回上下文中原有的工廠,如果重複刷新會拋出 IllegalStateException 異常;
  • 直接創建一個新工廠,然後替換上下文中原有的工廠;

1、刷新工廠

可重複刷新的上下文

不允許重複刷新的容器包括 GenericApplicationContext 與它的子類,或者更明確一點,除了 AbstractRefreshableApplicationContext 子類外的全部上下文:

@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());
}

不可重複刷新的上下文

可重複刷新的容器包括 AbstractRefreshableApplicationContext 的子類:

@Override
protected final void refreshBeanFactory() throws BeansException {、
    // 若已有工廠,則先銷燬單例然後關閉該工廠
    if (hasBeanFactory()) {
        destroyBeans();
        closeBeanFactory();
    }
    try {
        // 創建一個新工廠,並同步舊工廠的配置信息到新工廠,然後替換掉上下文中的舊工廠
        DefaultListableBeanFactory beanFactory = createBeanFactory();
        beanFactory.setSerializationId(getId());
        // 定製工廠的一些參數
        customizeBeanFactory(beanFactory);
        // 加載BeanDefinition
        loadBeanDefinitions(beanFactory);
        this.beanFactory = beanFactory;
    }
    catch (IOException ex) {
        throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
    }
}

2、向工廠註冊BeanDefinition

加載 BeanDefinition 這一步其實是在 refreshBeanFactory() 完成的,在 AbstractRefreshableApplicationContext 中他是一個抽象方法,而有三個子類實現了該方法,它們分別是:

  • AnnotationConfigWebApplicationContext
  • GroovyWebApplicationContext
  • XmlWebApplicationContext

光看着三個上下文的名稱,用腳指頭都能猜到,不同的配置環境會對應不同的實現類,實現類會在這一步根據不同的配置文件解析並加載 BeanDefinitionBeanFactory 裏。

我們以註解配置對應的 AnnotationConfigWebApplicationContext 爲例:

protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) {
    // BeanDefinition配置解析器
    AnnotatedBeanDefinitionReader reader = getAnnotatedBeanDefinitionReader(beanFactory);
    // class掃描器
    ClassPathBeanDefinitionScanner scanner = getClassPathBeanDefinitionScanner(beanFactory);

    // 名稱生成器
    BeanNameGenerator beanNameGenerator = getBeanNameGenerator();
    if (beanNameGenerator != null) {
        reader.setBeanNameGenerator(beanNameGenerator);
        scanner.setBeanNameGenerator(beanNameGenerator);
        beanFactory.registerSingleton(AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR, beanNameGenerator);
    }

    // 作用域解析器
    ScopeMetadataResolver scopeMetadataResolver = getScopeMetadataResolver();
    if (scopeMetadataResolver != null) {
        reader.setScopeMetadataResolver(scopeMetadataResolver);
        scanner.setScopeMetadataResolver(scopeMetadataResolver);
    }

    // 掃描在註解中聲明的Class
    if (!this.componentClasses.isEmpty()) {
        if (logger.isDebugEnabled()) {
            logger.debug("Registering component classes: [" +
                         StringUtils.collectionToCommaDelimitedString(this.componentClasses) + "]");
        }
        reader.register(ClassUtils.toClassArray(this.componentClasses));
    }

    // 掃描basePackage下的類型
    if (!this.basePackages.isEmpty()) {
        if (logger.isDebugEnabled()) {
            logger.debug("Scanning base packages: [" +
                         StringUtils.collectionToCommaDelimitedString(this.basePackages) + "]");
        }
        scanner.scan(StringUtils.toStringArray(this.basePackages));
    }

    // 獲取配置文件所在路基
    String[] configLocations = getConfigLocations();
    if (configLocations != null) {
        for (String configLocation : configLocations) {
            try {
                Class<?> clazz = ClassUtils.forName(configLocation, getClassLoader());
                if (logger.isTraceEnabled()) {
                    logger.trace("Registering [" + configLocation + "]");
                }
                reader.register(clazz);
            }
            catch (ClassNotFoundException ex) {
                if (logger.isTraceEnabled()) {
                    logger.trace("Could not load class for config location [" + configLocation +
                                 "] - trying package scan. " + ex);
                }
                int count = scanner.scan(configLocation);
                if (count == 0 && logger.isDebugEnabled()) {
                    logger.debug("No component classes found for specified class/package [" + configLocation + "]");
                }
            }
        }
    }
}

不同的實現對應不同的代碼,不過邏輯基本都一致,無外乎創建 Reader 然後解析配置文件/配置類,然後把配置中的 Bean 或者掃描配置的 class 或項目路徑上的 Class 得到的 Bean 加載到 BeanFactory 裏。

3、關閉舊工廠

若當前已有 BeanFactory,上下文會先嚐試移除該工廠,然後再新建工廠。

destroyBeans調用 將銷燬工廠中全部的單例 bean,並清空相關緩存,比如我們熟悉的三級緩存:

protected void destroyBeans() {
    getBeanFactory().destroySingletons();
}

@Override
public void destroySingletons() {
    // 調用DefaultSingletonBeanRegistry的destroySingletons方法
    super.destroySingletons();
    // 清空所有非空的bean緩存
    updateManualSingletonNames(Set::clear, set -> !set.isEmpty());
    clearByTypeCache();
}

// DefaultSingletonBeanRegistry
public void destroySingletons() {
    if (logger.isTraceEnabled()) {
        logger.trace("Destroying singletons in " + this);
    }
    synchronized (this.singletonObjects) {
        this.singletonsCurrentlyInDestruction = true;
    }

    // 根據bean名稱將bean從各個緩存中移除
    String[] disposableBeanNames;
    synchronized (this.disposableBeans) {
        disposableBeanNames = StringUtils.toStringArray(this.disposableBeans.keySet());
    }
    for (int i = disposableBeanNames.length - 1; i >= 0; i--) {
        destroySingleton(disposableBeanNames[i]);
    }

    // 清空三級緩存
    this.containedBeanMap.clear();
    this.dependentBeanMap.clear();
    this.dependenciesForBeanMap.clear();

    // 完全清空全部bean緩存
    clearSingletonCache();
}

然後再在 closeBeanFactory 方法中移除當前上下文對應舊 BeanFactory 實例的引用:

protected final void closeBeanFactory() {
    DefaultListableBeanFactory beanFactory = this.beanFactory;
    if (beanFactory != null) {
        beanFactory.setSerializationId(null);
        this.beanFactory = null;
    }
}

4、創建新工廠

新建工廠時,先通過 createBeanFactory 方法創建一個 DefaultListableBeanFactory,該 DefaultListableBeanFactory 會具備當前上下文的父工廠實例:

protected DefaultListableBeanFactory createBeanFactory() {
    return new DefaultListableBeanFactory(getInternalParentBeanFactory());
}
protected BeanFactory getInternalParentBeanFactory() {
    return (getParent() instanceof ConfigurableApplicationContext ?
            ((ConfigurableApplicationContext) getParent()).getBeanFactory() : getParent());
}

然後獲得 DefaultListableBeanFactory 實例後,再在通過 customizeBeanFactory 方法同步一些參數:

protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) {
    if (this.allowBeanDefinitionOverriding != null) {
        beanFactory.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
    }
    if (this.allowCircularReferences != null) {
        beanFactory.setAllowCircularReferences(this.allowCircularReferences);
    }
}

最後通過 loadBeanDefinitions 將 bean 的信息重新加載到新的工廠中。這裏需要注意的是,在 AbstractApplicationContextloadBeanDefinitions 是個抽象方法,根據子類的不同會有不同的實現,我們以比較熟悉的 XmlWebApplicationContext 爲例:

@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
    // 配置一個xml配置文件bean配置信息閱讀器
    XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
    beanDefinitionReader.setEnvironment(getEnvironment());
    beanDefinitionReader.setResourceLoader(this);
    beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
    initBeanDefinitionReader(beanDefinitionReader);
    // 使用閱讀器加載配置文件,並將其解析爲對應的bean信息
    loadBeanDefinitions(beanDefinitionReader);
}

protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {
    String[] configLocations = getConfigLocations();
    if (configLocations != null) {
        for (String configLocation : configLocations) {
            reader.loadBeanDefinitions(configLocation);
        }
    }
}

這裏的 Reader 與上下文的類型對應,或者說,上下文能讀取的配置就取決於 Reader 的類型,不同類型的上下文有不同的 Reader,比如 AnnotationConfigApplicationContext 中使用的就是 AnnotatedBeanDefinitionReader

這部分具體的解析流程會另外開一篇文章分析,這裏暫且知道是重新加載配置文件,獲取 bean 定義信息就行。

至此,舊的 BeanFactory 已經被新的 BeanFactory 替換,配置和數據都已經轉移到新的 BeanFactory 中。

三、對工廠進行預處理

現在,經過 refreshBeanFactory 方法的處理,舊的工廠被銷燬,新的 BeanFactory 也已經準備好了。接着,上下文需要通過 prepareBeanFactory 方法對這個新的工廠再進行初始化:

protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {
    // 配置類加載器,SpEL表達式解析器與屬性註冊器
    beanFactory.setBeanClassLoader(getClassLoader());
    beanFactory.setBeanExpressionResolver(new StandardBeanExpressionResolver(beanFactory.getBeanClassLoader()));
    beanFactory.addPropertyEditorRegistrar(new ResourceEditorRegistrar(this, getEnvironment()));

    // 添加後置處理器
    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.registerResolvableDependency(BeanFactory.class, beanFactory);
    beanFactory.registerResolvableDependency(ResourceLoader.class, this);
    beanFactory.registerResolvableDependency(ApplicationEventPublisher.class, this);
    beanFactory.registerResolvableDependency(ApplicationContext.class, this);

    // 添加默認的後置處理器ApplicationListenerDetector
    // 實現了ApplicationListener接口的bean會被記錄在上下文的applicationListeners列表
    // 並且指定方法調用的時候會廣播事件
    beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(this));

    // 如果存在名爲LoadTimeWeaver的bean,說明需要添加後置處理器LoadTimeWeaverAwareProcessor
    // 被實現LoadTimeWeaverAware類設置LoadTimeWeaver實例
    if (beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
        beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
        beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
    }

    // 若存在environment,systemProperties與SystemEnvironment這三個bean,則默認註冊到容器
    if (!beanFactory.containsLocalBean(ENVIRONMENT_BEAN_NAME)) {
        beanFactory.registerSingleton(ENVIRONMENT_BEAN_NAME, getEnvironment());
    }
    if (!beanFactory.containsLocalBean(SYSTEM_PROPERTIES_BEAN_NAME)) {
        beanFactory.registerSingleton(SYSTEM_PROPERTIES_BEAN_NAME, getEnvironment().getSystemProperties());
    }
    if (!beanFactory.containsLocalBean(SYSTEM_ENVIRONMENT_BEAN_NAME)) {
        beanFactory.registerSingleton(SYSTEM_ENVIRONMENT_BEAN_NAME, getEnvironment().getSystemEnvironment());
    }
}

這個方法主要用於爲 BeanFactory 添加默認的後置處理器,與一些默認的 bean 實例。

默認實例

需要注意的是,正常情況下,我們通過容器獲取到的 bean 都是根據 BeanDefinition 創建的,而在這裏註冊的四個 bean :

  • ResourceLoader
  • BeanFactory
  • ApplicationEventPublisher
  • ApplicationContext

這四個類要麼是容器本身,要麼是容器本身持有的成員變量,因此都是沒有對應 BeanDefinition 的。

總結

BeanFactory 的初始化共分爲三個方法,對應三個主要過程:

  1. prepareRefresh:初始化上下文的屬性以及一些狀態;
  2. obtainFreshBeanFactory:銷燬上下文中的舊 BeanFactory,然後創建新的 BeanFactory 對象;
  3. prepareBeanFactory:爲新 BeanFactory 配置一些基本的參數,包括註冊一些默認 Bean、Bean 作用域以及一些後置處理器,以及根據配置文件加載 BeanDefinition
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章