Spring5源碼分析(二) IOC 容器的初始化(一)

一,概述

  IOC 容器的初始化包括 BeanDefinition 的 Resource 定位、載入和註冊這三個基本的過程。咱們以 ApplicationContext 爲例講解來深刻講解,ApplicationContext系列容器也許是咱們最熟悉的,由於 Web項目中使用的XmlWebApplicationContext就屬於這個繼承體系,還有
ClasspathXmlApplicationContext 等,其繼承體系以下圖所示:
這裏寫圖片描述java

ApplicationContext容許上下文嵌套,經過保持父上下文能夠維持一個上下文體系。對於 Bean 的查找能夠在這個上下文體系中發生,首先檢查當前上下文,其次是父上下文,逐級向上,這樣爲不一樣的Spring 應用提供了一個共享的 Bean 定義環境。設計模式

  下面咱們分別簡單地演示一下兩種 IOC 容器的建立過程,一種是XmlBeanFactory 一種是FileSystemXmlApplicationContext 其中XmlBeanFactory已經被標記爲過期的僅僅作簡單講述由於在Spring5.0中XmlBeanFactory已經被標記爲過期的,爲何還要說呢!由於XmlBeanFactory相對純粹也相對簡單。數組

二,XmlBeanFactory IOC容器建立的流程

經過 XmlBeanFactory 的源碼,咱們能夠發現: 併發

public class XmlBeanFactory extends DefaultListableBeanFactory {
private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);
    public XmlBeanFactory(Resource resource) throws BeansException {
    this(resource, null);
    }
    public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
    super(parentBeanFactory);
    this.reader.loadBeanDefinitions(resource);
    }
}

定位是根據Resource來完成,載入是根據BeanDefinitionReader,剩下就是註冊,註冊是根據ListableBeanFactory。咱們能夠根據下面的代碼跟進 去,去理解定位、載入、註冊的全過程。app

// 根據 Xml 配置文件建立 Resource 資源對象,該對象中包含了 BeanDefinition 的信息
ClassPathResource resource = new ClassPathResource("application-context.xml");
// 建立 DefaultListableBeanFactory
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
//建立 XmlBeanDefinitionReader 讀取器,用於載入 BeanDefinition。
// 之因此須要 BeanFactory 做爲參數,是由於會將讀取的信息回調配置給 factory
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
// XmlBeanDefinitionReader 執行載入 BeanDefinition 的方法,最後會完成 Bean 的載入和註冊。
// 完成後 Bean 就成功的放置到 IOC 容器當中,之後咱們就能夠從中取得 Bean 來使用
reader.loadBeanDefinitions(resource);

經過前面的源碼,XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);中其中 this 傳的是 factory 對象ide

下面我給出本身跟進去的時序圖裏面標紅的爲主要的方法須要仔細看:
這裏寫圖片描述
具體類這裏不作詳細的講解,下面FileSystemXmlApplicationContext 容器建立會對重要方法作詳細說明。函數

二,FileSystemXmlApplicationContext IOC容器建立的流程

ApplicationContext = new FileSystemXmlApplicationContext(xmlPath);post

先看其構造函數的調用:this

public FileSystemXmlApplicationContext(String configLocation) throws BeansException {
        this(new String[] {configLocation}, true, null);
}

其實際調用的構造函數爲:spa

public FileSystemXmlApplicationContext(
        String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
        throws BeansException {

    super(parent);
    setConfigLocations(configLocations);
    if (refresh) {
        refresh();
    }
}

2.1 設置資源加載器和資源定位

  經過分析 FileSystemXmlApplicationContext的源代碼能夠知道,在建立FileSystemXmlApplicationContext容器時,構造方法作如下兩項重要工做:

首先,調用父類容器的構造方法(super(parent)方法)爲容器設置好 Bean 資源加載器。

而後,再調用父類 AbstractRefreshableConfigApplicationContext 的
setConfigLocations(configLocations)方法設置Bean定義資源文件的定位路徑。

經過追蹤FileSystemXmlApplicationContext的繼承體系 ,發現其父類的父類AbstractApplicationContext中初始化IOC 容器所作的主要源碼以下:

public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext {
//靜態初始化塊,在整個容器建立過程當中只執行一次
static {
//爲了不應用程序在 Weblogic8.1 關閉時出現類加載異常加載問題,加載 IOC 容
//器關閉事件(ContextClosedEvent)類
ContextClosedEvent.class.getName();
}
public AbstractApplicationContext() {
this.resourcePatternResolver = getResourcePatternResolver();
}
public AbstractApplicationContext(@Nullable ApplicationContext parent) {
this();
setParent(parent);
}
//獲取一個 Spring Source 的加載器用於讀入 Spring Bean 定義資源文件
protected ResourcePatternResolver getResourcePatternResolver() {
//AbstractApplicationContext 繼承 DefaultResourceLoader,所以也是一個資源加載器
//Spring 資源加載器,其 getResource(String location)方法用於載入資源
return new PathMatchingResourcePatternResolver(this);
}

}

AbstractApplicationContext構造方法中調用PathMatchingResourcePatternResolver 的構造方法建立 Spring 資源加載器:

public PathMatchingResourcePatternResolver(ResourceLoader resourceLoader) {
Assert.notNull(resourceLoader, "ResourceLoader must not be null");
//設置 Spring 的資源加載器
this.resourceLoader = resourceLoader;
}

在設置容器的資源加載器以後 , 接下來FileSystemXmlApplicationContext 執行setConfigLocations 方法經過調用其父類 AbstractRefreshableConfigApplicationContext 的方法進行對 Bean 定義資源文件的定位,該方法的源碼以下:

//處理單個資源文件路徑爲一個字符串的狀況
public void setConfigLocation(String location) {
//String CONFIG_LOCATION_DELIMITERS = ",; /t/n";
//即多個資源文件路徑之間用」 ,; \t\n」分隔,解析成數組形式
setConfigLocations(StringUtils.tokenizeToStringArray(location, CONFIG_LOCATION_DELIMITERS));
}
//解析 Bean 定義資源文件的路徑,處理多個資源文件字符串數組
public void setConfigLocations(@Nullable String... locations) {
    if (locations != null) {
    Assert.noNullElements(locations, "Config locations must not be null");
    this.configLocations = new String[locations.length];
        for (int i = 0; i < locations.length; i++) {
        // resolvePath 爲同一個類中將字符串解析爲路徑的方法
        this.configLocations[i] = resolvePath(locations[i]).trim();
        }
    }
    else {
       this.configLocations = null;
    }
}

經過這兩個方法的源碼咱們能夠看出,咱們既可使用一個字符串來配置多個 Spring Bean 定義資源文件,也可使用字符串數組,即下面兩種方式都是能夠的:
  a:ClasspathResourceres=new ClasspathResource(「a.xml,b.xml,……」);
多個資源文件路徑之間能夠是用」 , ; \t\n」等分隔。
  b:ClasspathResourceres=new ClasspathResource(newString[]{「a.xml」,」b.xml」,……});
至此,SpringIOC 容器在初始化時將配置的 Bean 定義資源文件定位爲 Spring 封裝的 Resource。

2.2 AbstractApplicationContext 的 refresh 函數載入Bean定義過程

SpringIOC 容器對 Bean定義資源的載入是從refresh()函數開始的,refresh()是一個模板方法,refresh()方法的做用是:在建立 IOC 容器前,若是已經有容器存在,則須要把已有的容器銷燬和關閉,以保證在 refresh 以後使用的是新創建起來的 IOC 容器。refresh 的做用相似於對 IOC容器的重啓,在新創建好的容器中對容器進行初始化,對 Bean 定義資源進行載入 FileSystemXmlApplicationContext經過調用其父類AbstractApplicationContext的refresh()函數啓動整個 IOC 容器對 Bean 定義的載入過程:

public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        //調用容器準備刷新的方法,獲取容器的當時時間,同時給容器設置同步標識
        prepareRefresh();
        //告訴子類啓動 refreshBeanFactory()方法,Bean 定義資源文件的載入從
        //子類的 refreshBeanFactory()方法啓動
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
        //爲 BeanFactory 配置容器特性,例如類加載器、事件處理器等
        prepareBeanFactory(beanFactory);
     try {
        //爲容器的某些子類指定特殊的 BeanPost 事件處理器
        postProcessBeanFactory(beanFactory);
        //調用全部註冊的 BeanFactoryPostProcessor 的 Bean
        invokeBeanFactoryPostProcessors(beanFactory);
        //爲 BeanFactory 註冊 BeanPost 事件處理器.
        //BeanPostProcessor 是 Bean 後置處理器,用於監聽容器觸發的事件
        registerBeanPostProcessors(beanFactory);
        //初始化信息源,和國際化相關.
        initMessageSource();
        //初始化容器事件傳播器.
        initApplicationEventMulticaster();
        //調用子類的某些特殊 Bean 初始化方法
        onRefresh();
        //爲事件傳播器註冊事件監聽器.
        registerListeners();
        //初始化全部剩餘的單例 Bean
        finishBeanFactoryInitialization(beanFactory);
        //初始化容器的生命週期事件處理器,併發布容器的生命週期事件
        finishRefresh();
    }
    catch (BeansException ex) {
        if (logger.isWarnEnabled()) {
        logger.warn("Exception encountered during context initialization - " +
        "cancelling refresh attempt: " + ex);
        }
        //銷燬已建立的 Bean
        destroyBeans();
        //取消 refresh 操做,重置容器的同步標識.
        cancelRefresh(ex);

        throw ex;
    }
    finally {
        resetCommonCaches();
    }
 }
}

refresh()方法主要爲 IOC 容器 Bean 的生命週期管理提供條件,Spring IOC 容器載入 Bean 定義資源文件從其子類容器的refreshBeanFactory() 方法啓動 , 因此整個refresh() 中「ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();」這句之後代碼的都是註冊容器的信息源和生命週期事件,載入過程就是從這句代碼啓動。

  refresh()方法的做用是:在建立IOC容器前,若是已經有容器存在,則須要把已有的容器銷燬和關閉,以保證在refresh以後使用的是新創建起來的 IOC 容器。refresh的做用相似於對IOC容器的重啓,在新創建好的容器中對容器進行初始化,對 Bean 定義資源進行載入

#### 2.3 AbstractApplicationContext 的 obtainFreshBeanFactory() 方法調用子類容器的refreshBeanFactory()方法,啓動容器載入 Bean 定義資源文件的過程,代碼以下:

protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
//這裏使用了委派設計模式,父類定義了抽象的 refreshBeanFactory()方法,具體實現調用子類容器的 refreshBeanFactory()方
法
refreshBeanFactory();
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
if (logger.isDebugEnabled()) {
logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
}
return beanFactory;
}

AbstractApplicationContext類中只抽象定義了refreshBeanFactory()方法,容器真正調用的是其子類 AbstractRefreshableApplicationContext 實現的 refreshBeanFactory()方法,方法的源碼以下:

@Override
    protected final void refreshBeanFactory() throws BeansException {
        //若是已經有容器,銷燬容器中的bean,關閉容器
        if (hasBeanFactory()) {
            destroyBeans();
            closeBeanFactory();
        }
        try {
            //建立IOC容器
            DefaultListableBeanFactory beanFactory = createBeanFactory();
            beanFactory.setSerializationId(getId());
            //對IOC容器進行定製化,如設置啓動參數,開啓註解的自動裝配等
            customizeBeanFactory(beanFactory);
            //調用載入Bean定義的方法,主要這裏又使用了一個委派模式,在當前類中只定義了抽象的loadBeanDefinitions方法,具體的實現調用子類容器
            loadBeanDefinitions(beanFactory);
            synchronized (this.beanFactoryMonitor) {
                this.beanFactory = beanFactory;
            }
        }
        catch (IOException ex) {
            throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
        }
    }

在這個方法中,先判斷 BeanFactory 是否存在,若是存在則先銷燬beans 並關閉 beanFactory,接着建立DefaultListableBeanFactory,並調用loadBeanDefinitions(beanFactory)裝載 bean 定義。

2.4 AbstractRefreshableApplicationContext子類的loadBeanDefinitions 方法

AbstractRefreshableApplicationContext中只定義了抽象的loadBeanDefinitions方法,容器真正調用的是其子類AbstractXmlApplicationContext 對該方法的實現 ,AbstractXmlApplicationContext的主要源碼以下:

@Override
    protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
        // Create a new XmlBeanDefinitionReader for the given BeanFactory.
        //建立XmlBeanDefinitionReader,即建立Bean讀取器,並經過回調設置到容器中去,容 器使用該讀取器讀取Bean定義資源
        XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

        // Configure the bean definition reader with this context's
        // resource loading environment.
        //爲Bean讀取器設置Spring資源加載器,AbstractXmlApplicationContext的
        //祖先父類AbstractApplicationContext繼承DefaultResourceLoader,所以,容器自己也是一個資源加載器
        beanDefinitionReader.setEnvironment(this.getEnvironment());
        beanDefinitionReader.setResourceLoader(this);
        //爲Bean讀取器設置SAX xml解析器
        beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

        // Allow a subclass to provide custom initialization of the reader,
        // then proceed with actually loading the bean definitions.
        //當Bean讀取器讀取Bean定義的Xml資源文件時,啓用Xml的校驗機制
        initBeanDefinitionReader(beanDefinitionReader);
        //Bean讀取器真正實現加載的方法
        loadBeanDefinitions(beanDefinitionReader);
    }

    protected void initBeanDefinitionReader(XmlBeanDefinitionReader reader) {
            reader.setValidating(this.validating);
    }


  //Xml Bean讀取器加載Bean定義資源
 protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
        //獲取Bean定義資源的定位
        Resource[] configResources = getConfigResources();
        if (configResources != null) {
            //Xml Bean讀取器調用其父類AbstractBeanDefinitionReader讀取定位
            //的Bean定義資源
            reader.loadBeanDefinitions(configResources);
        }
        //若是子類中獲取的Bean定義資源定位爲空,則獲取FileSystemXmlApplicationContext構造方法中setConfigLocations方法設置的資源
        String[] configLocations = getConfigLocations();
        if (configLocations != null) {
            //Xml Bean讀取器調用其父類AbstractBeanDefinitionReader讀取定位
            //的Bean定義資源
            reader.loadBeanDefinitions(configLocations);
        }
  }

//這裏又使用了一個委託模式,調用子類的獲取Bean定義資源定位的方法
//該方法在ClassPathXmlApplicationContext中進行實現,對於咱們
//舉例分析源碼的FileSystemXmlApplicationContext沒有使用該方法
@Nullable
protected Resource[] getConfigResources() {
    return null;
}

XmlBean 讀取器(XmlBeanDefinitionReader)調用其父類AbstractBeanDefinitionReader的reader.loadBeanDefinitions方法讀取Bean定義資源。因爲咱們使用 FileSystemXmlApplicationContext做爲例子分析,所以getConfigResources的返回值爲null,所以程序執行reader.loadBeanDefinitions(configLocations)分支。

因爲IOC 容器的初始化內容比較多一次文章沒法寫完,因此分了幾篇進行講解此篇爲第一篇。

文檔有參考其餘資料,若是問題請聯繫我,進行刪除!

相關文章
相關標籤/搜索