1. 程式人生 > >【簡記】Java Web 內幕——Spring原始碼(元件分析,BeanFactory原始碼,Bean建立之前)

【簡記】Java Web 內幕——Spring原始碼(元件分析,BeanFactory原始碼,Bean建立之前)

本章內容:

  • Bean元件、Context元件解析
  • BeanFactory的建立
  • 初始化Bean例項之前的操作

Bean元件解析

Spring Bean 的建立是典型的工廠模式, 它的頂級介面是BeanFactory。

Bean工廠的類層次關係圖:
這裡寫圖片描述

4個介面,共同定義了Bean 的集合、Bean 之間的關係和Bean 的行為。

Bean定義的類層次關係圖:
這裡寫圖片描述
Bean 的定義完整地描述了在Spring 的配置檔案中你定義的< /bean>節點中所有的資訊,包括各種子節點。

Context元件分析

這裡寫圖片描述

ApplicationContext 繼承了BeanFactory,這也說明了Spring 容
器中執行的主體物件是Bean 。另外ApplicationContext 繼承了Re sourceLoader 介面,使得ApplicationContext 可以訪問到任何外部資源。

ApplicationContext 必須要完成以下幾件事情:

  • 標識一個應用環境。
  • 利用Bean Factory 建立Bean 物件。
  • 儲存物件關係表。
  • 能夠捕獲各種事件。

建立BeanFactory工廠

refresh方法(位於AbstractApplicationContext),整個Spring Bean載入的核心,構建了Bean關係網(也就是Bean Factory)

    public void refresh() throws BeansException, IllegalStateException {
        synchronized (this
.startupShutdownMonitor) { // 為重新整理準備新的context prepareRefresh(); // 重新整理所有BeanFactory子容器 ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); //建立BeanFactory prepareBeanFactory(beanFactory); try { //設定BeanFactoy的後置處理
postProcessBeanFactory(beanFactory); //呼叫BeanFactory的後處理器,這些後處理器是在Bean定義中向容器註冊的 invokeBeanFactoryPostProcessors(beanFactory); //註冊Bean的後處理器,在Bean建立過程中呼叫。 registerBeanPostProcessors(beanFactory); //對上下文中的訊息源進行初始化 initMessageSource(); //初始化上下文中的事件機制 initApplicationEventMulticaster(); //初始化其他的特殊Bean onRefresh(); //檢查監聽Bean並且將這些Bean向容器註冊 registerListeners(); //例項化所有的(non-lazy-init)單件 finishBeanFactoryInitialization(beanFactory); //釋出容器事件,結束Refresh過程 finishRefresh(); } catch (BeansException ex) { //為防止Bean資源佔用,在異常處理中,銷燬已經在前面過程中生成的單件Bean destroyBeans(); // 重置 'active'標誌 cancelRefresh(ex); throw ex; } finally { // Reset common introspection caches in Spring's core, since we // might not ever need metadata for singleton beans anymore... resetCommonCaches(); } } }

首先refresh()方法有幾點是值得我們學習的:

1、方法是加鎖的,這麼做的原因是避免多執行緒同時重新整理Spring上下文

2、儘管加鎖可以看到是針對整個方法體的,但是沒有在方法前加synchronized關鍵字,而使用了物件鎖startUpShutdownMonitor,這樣做有兩個好處:

(1)refresh()方法和close()方法都使用了startUpShutdownMonitor物件鎖加鎖,這就保證了在呼叫refresh()方法的時候無法呼叫close()方法,反之亦然,避免了衝突

(2)另外一個好處不在這個方法中體現,但是提一下,使用物件鎖可以減小了同步的範圍,只對不能併發的程式碼塊進行加鎖,提高了整體程式碼執行的效率

3、方法裡面使用了每個子方法定義了整個refresh()方法的流程,使得整個方法流程清晰易懂。

refresh方法主要包含了以下步驟:
( 1 )構建BeanFactory ,以便於產生所需的”演員” 。
( 2 )註冊可能感興趣的事件。
( 3 )建立Bean 例項物件。
( 4 )觸發被監聽的事件。

建立和配置BeanFactory的核心程式碼(在obtainFreshBeanFactory()方法中被呼叫,位於AbstractRefreshableApplicationContext類中)

protected final void refreshBeanFactory() throws BeansException {  
    //這裡判斷,如果已經建立了BeanFactory,則銷燬並關閉該BeanFactory  
    if (hasBeanFactory()) {  
        destroyBeans();  
        closeBeanFactory();  
    }  
    //這裡是建立並設定持有的DefaultListableBeanFactor的地方同時呼叫  
    //loadBeanDefinitions再載入BeanDefinition的資訊  
    try {  
        DefaultListableBeanFactory beanFactory = createBeanFactory();  
        beanFactory.setSerializationId(getId());  
        customizeBeanFactory(beanFactory);  
        loadBeanDefinitions(beanFactory);             
        synchronized (this.beanFactoryMonitor) {  
            this.beanFactory = beanFactory;  
        }  
    }  
    catch (IOException ex) {  
        throw new ApplicationContextException("I/O error parsing XML document for "  
        + getDisplayName(), ex);  
    }  
}  
//這就是在上下文中建立DefaultListableBeanFactory的地方,而getInternalParentBeanFactory()  
//的具體實現可以  
//參看AbstractApplicationContext中的實現,會根據容器已有的雙親IoC容器的資訊來生成  
// DefaultListableBeanFactory的雙親IoC容器  
protected DefaultListableBeanFactory createBeanFactory() {  
    return new DefaultListableBeanFactory(getInternalParentBeanFactory());  
}  

BeanDefinition的載入和解析(以AbstractXmlApplicationContext為例)

//這裡是使用BeanDefinitionReader載入Bean定義的地方,因為允許有多種載入方式,雖然用得  
//最多的是XML定義的形式,這裡通過一個抽象函式把具體的實現委託給子類來完成  
    protected abstract void loadBeanDefinitions(DefaultListableBeanFactory beanFactory)  
    throws IOException, BeansException;  

//以AbstractXmlApplicationContext為例
//這裡是實現loadBeanDefinitions的地方  
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws IOException {  
    //建立XmlBeanDefinitionReader,並通過回撥設定到BeanFactory中去,建立BeanFactory  
    //的過程可以參考上文對程式設計式使用IoC容器的相關分析,這裡和前面一樣,使用的也是  
    DefaultListableBeanFactory  
    XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinition  
    Reader(beanFactory);  
    //這裡設定XmlBeanDefinitionReader,為XmlBeanDefinitionReader配  
    //ResourceLoader,因為DefaultResourceLoader是父類,所以this可以直接被使用  
    beanDefinitionReader.setResourceLoader(this);  
    beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));  
    //這是啟動Bean定義資訊載入的過程  
    initBeanDefinitionReader(beanDefinitionReader);  
    loadBeanDefinitions(beanDefinitionReader);  
}  

接著就是loadBeanDefinitions呼叫的地方,首先得到BeanDefinition資訊的Resource定位,然後直接呼叫 XmlBeanDefinitionReader來讀取,具體的載入過程是委託給BeanDefinitionReader完成的。因為這裡的BeanDefinition是通過XML檔案定義的,所以這裡使用XmlBeanDefinitionReader來載入BeanDefinition到容器中。

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

getConfigLocations會先去找父類中定義的預設配置檔案,如果沒有,則呼叫實現類中的getDefaultConfigLocations()方法

    protected String[] getDefaultConfigLocations() {
        if (getNamespace() != null) {
            return new String[] {DEFAULT_CONFIG_LOCATION_PREFIX + getNamespace() + DEFAULT_CONFIG_LOCATION_SUFFIX};
        }
        else {
            return new String[] {DEFAULT_CONFIG_LOCATION};
        }
    }

上面的DEFAULT_CONFIG_LOCATION就是/WEB-INF/applicationContext.xml


    public int loadBeanDefinitions(String location, Set actualResources) throws  
    BeanDefinitionStoreException {  
    //這裡取得 ResourceLoader,使用的是DefaultResourceLoader  
    ResourceLoader resourceLoader = getResourceLoader();  
    if (resourceLoader == null) {  
        throw new BeanDefinitionStoreException(  
               "Cannot import bean definitions from location [" + location + "]:   
               no ResourceLoader available");  
    }  
//這裡對Resource的路徑模式進行解析,比如我們設定的各種Ant格式的路徑定義,得到需要的  
//Resource集合,這些Resource集合指向我們已經定義好的BeanDefinition資訊,可以是多個檔案  
    if (resourceLoader instanceof ResourcePatternResolver) {  
        try {  
//呼叫DefaultResourceLoader的getResource完成具體的Resource定位  
            Resource[] resources = ((ResourcePatternResolver) resourceLoader).  
            getResources(location);  
            int loadCount = loadBeanDefinitions(resources);  
            if (actualResources != null) {  
                for (int i = 0; i < resources.length; i++) {  
                    actualResources.add(resources[i]);  
                }  
            }  
            if (logger.isDebugEnabled()) {  
                logger.debug("Loaded " + loadCount + " bean definitions from  
                location pattern [" + location + "]");  
            }  
            return loadCount;  
        }  
        catch (IOException ex) {  
            throw new BeanDefinitionStoreException(  
                  "Could not resolve bean definition resource pattern  
                  [" + location + "]", ex);  
        }  
    }  
    else {  
        // 呼叫DefaultResourceLoader的getResource完成具體的Resource定位  
        Resource resource = resourceLoader.getResource(location);  
        int loadCount = loadBeanDefinitions(resource);  
        if (actualResources != null) {  
            actualResources.add(resource);  
        }  
        if (logger.isDebugEnabled()) {  
            logger.debug("Loaded " + loadCount + " bean definitions from location  
            [" + location + "]");  
        }  
        return loadCount;  
    }  
}  

Resource的定位

//對於取得Resource的具體過程,我們可以看看DefaultResourceLoader是怎樣完成的  
public Resource getResource(String location) {  
    Assert.notNull(location, "Location must not be null");  
    //這裡處理帶有classpath標識的Resource  
    if (location.startsWith(CLASSPATH_URL_PREFIX)) {  
        return new ClassPathResource(location.substring(CLASSPATH_  
        URL_PREFIX.length()), getClassLoader());  
    }  
    else {  
        try {  
            // 這裡處理URL標識的Resource定位  
            URL url = new URL(location);  
            return new UrlResource(url);  
        }  
        catch (MalformedURLException ex) {  
    //如果既不是classpath,也不是URL標識的Resource定位,則把getResource的  
    //重任交給getResourceByPath,這個方法是一個protected方法,預設的實現是得到  
    //一個ClassPathContextResource,這個方法常常會用子類來實現  
    return getResourceByPath(location);  
        }  
    }  
} 

getResourceByPath會被子類FileSystemXmlApplicationContext實現,這個方法返回的是一個 FileSystemResource物件,通過這個物件,Spring可以進行相關的I/O操作,完成BeanDefinition的定位。

Spring的BeanDefinion是怎樣按照Spring的Bean語義要求進行解析並轉化為容器內部資料結構的?

這個過程是在registerBeanDefinitions(doc, resource)中完成的。具體的過程是由BeanDefinitionDocumentReader來完成的,這個registerBeanDefinition還對載入的Bean的數量進行了統計。

具體的Spring BeanDefinition的解析是在BeanDefinitionParserDelegate(是一個類)中完成的。這個類裡包含了對各種Spring Bean定義規則的處理。解析完成以後,會把解析結果放到BeanDefinition物件中並設定到BeanDefinitionHolder中去。

beanClass、description、lazyInit這些屬性都是在配置bean時經常碰到的,都集中在這裡。這個BeanDefinition是IoC容器體系中非常重要的核心資料結構。通過解析以後,這些資料已經做好在IoC容器裡大顯身手的準備了。

public AbstractBeanDefinition parseBeanDefinitionElement(  
        Element ele, String beanName, BeanDefinition containingBean) {  
    this.parseState.push(new BeanEntry(beanName));  
    //這裡只讀取定義的<bean>中設定的class名字,然後載入到BeanDefinition中去,只是做個  
    //記錄,並不涉及物件的例項化過程,物件的例項化實際上是在依賴注入時完成的  
    String className = null;  
    if (ele.hasAttribute(CLASS_ATTRIBUTE)) {  
        className = ele.getAttribute(CLASS_ATTRIBUTE).trim();  
    }  
    try {  
        String parent = null;  
        if (ele.hasAttribute(PARENT_ATTRIBUTE)) {  
            parent = ele.getAttribute(PARENT_ATTRIBUTE);  
        }  
        //這裡生成需要的BeanDefinition物件,為Bean定義資訊的載入做準備  
        AbstractBeanDefinition bd = createBeanDefinition(className, parent);  
        //這裡對當前的Bean元素進行屬性解析,並設定description的資訊  
        parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);  
        bd.setDescription(DomUtils.getChildElementValueByTagName(ele,   
        DESCRIPTION_ELEMENT));  
        //從名字可以清楚地看到,這裡是對各種<bean>元素的資訊進行解析的地方  
        parseMetaElements(ele, bd);  
        parseLookupOverrideSubElements(ele, bd.getMethodOverrides());  
        parseReplacedMethodSubElements(ele, bd.getMethodOverrides());  
        //解析<bean>的建構函式設定  
        parseConstructorArgElements(ele, bd);  
        //解析<bean>的property設定  
        parsePropertyElements(ele, bd);  
        parseQualifierElements(ele, bd);  
        bd.setResource(this.readerContext.getResource());  
        bd.setSource(extractSource(ele));  
        return bd;  
    }  
//下面這些異常是在配置Bean出現問題時經常會看到的,原來是在這裡丟擲的這些檢查是在  
//createBeanDefinition時進行的,會檢查Bean的class設定是否正確,比如這個類是否能找到  
    catch (ClassNotFoundException ex) {  
        error("Bean class [" + className + "] not found", ele, ex);  
    }  
    catch (NoClassDefFoundError err) {  
        error("Class that bean class [" + className + "] depends on not found", ele, err);  
    }  
    catch (Throwable ex) {  
        error("Unexpected failure during bean definition parsing", ele, ex);  
    }  
    finally {  
        this.parseState.pop();  
    }  
    return null;  
} 

BeanDefinition在IoC容器中的註冊
在DefaultListableBeanFactory中,是通過一個HashMap來持有載入的BeanDefinition的,這個HashMap的定義在DefaultListableBeanFactory中可以看到,如下所示。

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

有用到synchronized關鍵字,當有Bean正在建立,對beanDefinitionMap加鎖,再進行bean定義寫入

完成了BeanDefinition的註冊,就完成了IoC容器的初始化過程。此時,在使用的IoC容器DefaultListableBeanFactory中已經建立了整個Bean的配置資訊,而且這些BeanDefinition已經可以被容器使用了,它們都在beanDefinitionMap裡被檢索和使用。容器的作用就是對這些資訊進行處理和維護。這些資訊是容器建立依賴反轉的基礎,有了這些基礎資料,下面我們看一下在IoC容器中,依賴注入是怎樣完成的。

建立bean例項之前的一些操作

PrepareBeanFactory方法:

如果自定義的bean中沒有名為”systemProperties”和”systemEnvironment”的Bean,則註冊兩個Bean,Key為”systemProperties”和”systemEnvironment”,Value為Map,這兩個Bean就是一些系統配置和系統環境資訊。

invokeBeanFactoryPostProcessors方法:

這個是整個Spring流程中非常重要的一部分,是Spring留給使用者的一個非常有用的擴充套件點,BeanPostProcessor介面針對的是每個Bean初始化前後做的操作而BeanFactoryPostProcessor介面針對的是所有Bean例項化前的操作,注意用詞,初始化只是例項化的一部分,表示的是呼叫Bean的初始化方法,BeanFactoryPostProcessor介面方法呼叫時機是任意一個自定義的Bean被反射生成出來前。

  • 有一些選項在設定好後通常就不會去變更,而有一些選項可能得隨時調整,這時候如果能提供一個更簡潔的設定,提供一些常用選項在其中隨時更改,這樣的程式在使用時會更有彈性
  • 一種情況是,XML定義檔中定義了一些低許可權程式使用人員可以設定的選項,然而高許可權的程式管理員可以透過屬性檔案的設定,來推翻低許可權程式使用人員的設定,以完成高許可權管理的統一性。
        List<BeanFactoryPostProcessor> priorityOrderedPostProcessors = new ArrayList<BeanFactoryPostProcessor>();
        List<String> orderedPostProcessorNames = new ArrayList<String>();
        List<String> nonOrderedPostProcessorNames = new ArrayList<String>();
        for (String ppName : postProcessorNames) {
            if (processedBeans.contains(ppName)) {
                // skip - already processed in first phase above
            }
            else if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
                priorityOrderedPostProcessors.add(beanFactory.getBean(ppName, BeanFactoryPostProcessor.class));
            }
            else if (beanFactory.isTypeMatch(ppName, Ordered.class)) {
                orderedPostProcessorNames.add(ppName);
            }
            else {
                nonOrderedPostProcessorNames.add(ppName);
            }
        }

這裡分出了三個List,表示開發者可以自定義BeanFactoryPostProcessor的呼叫順序,具體為呼叫順序為:

  • 如果BeanFactoryPostProcessor實現了PriorityOrdered介面(PriorityOrdered介面是Ordered的子介面,沒有自己的介面方法定義,只是做一個標記,表示呼叫優先順序高於Ordered介面的子介面),是優先順序最高的呼叫,呼叫順序是按照介面方法getOrder()的實現,對返回的int值從小到大進行排序,進行呼叫
  • 如果BeanFactoryPostProcessor實現了Ordered介面,是優先順序次高的呼叫,將在所有實現PriorityOrdered介面的BeanFactoryPostProcessor呼叫完畢之後,依據getOrder()的實現對返回的int值從小到大排序,進行呼叫
  • 不實現Ordered介面的BeanFactoryPostProcessor在上面的BeanFactoryPostProcessor呼叫全部完畢之後進行呼叫,呼叫順序就是Bean定義的順序

registerBeanPostProcessors方法

整體程式碼思路和invokeBeanFactoryPostProcessors方法類似,但是這裡不會呼叫BeanPostProcessor介面的方法,而是把每一個BeanPostProcessor介面實現類例項化出來並按照順序放入一個List中,到時候按順序進行呼叫。

具體程式碼思路可以參考invokeBeanFactoryPostProcessors,這裡就根據程式碼總結一下BeanPostProcessor介面的呼叫順序:

  1. 優先呼叫PriorityOrdered介面的子介面,呼叫順序依照介面方法getOrder的返回值從小到大排序
  2. 其次呼叫Ordered介面的子介面,呼叫順序依照介面方法getOrder的返回值從小到大排序
  3. 接著按照BeanPostProcessor實現類在配置檔案中定義的順序進行呼叫
  4. 最後呼叫MergedBeanDefinitionPostProcessor介面的實現Bean,同樣按照在配置檔案中定義的順序進行呼叫

initMessageSource方法

initMessageSource方法用於初始化MessageSource,MessageSource是Spring定義的用於實現訪問國際化的介面

initApplicationEventMulticaster方法

初始化上下文事件廣播器

onRefresh方法

一個模板方法,重寫它的作用是新增特殊上下文重新整理的工作,在特殊Bean的初始化時、初始化之前被呼叫。

把loc 容器比作一個箱子,在這個箱子裡有若干個球的模子,可以用這些模子來造很多種不同的球,還有一個造這些球模的機器,這個機器可以產生球模。那麼它們的對應關係就是BeanFactory ,即那個造球模的機器;球模就是Bean ,而球模造出來的球就是Bean
的例項。前面所說的幾個擴充套件點又在什麼地方呢?
BeanFactoryPostProcessor 對應到當造球模被造出來時, 此時你將有機會對其做出適當的修正,也就是它可以幫你修改球模。
而InitializingBean 和DisposableBean 是在球模造球的開始和結束階段,你可以完成一些預備和掃尾工作。
BeanPostProcessor 可以讓你對球模造出來的球做出適當的修正。最後還有一個FactoryBean ,它可是一個神奇的球模。這個球模不是預先就定型的,而是由你來確定它的形狀。既然你可以確定這個球模型的形狀,那麼它造出來的球肯定就是你想要的球了,這樣在這個箱子裡面可以發現所有你想要的球。