1. 程式人生 > >Spring4.3.x 淺析xml配置的解析過程(1)——使用XmlBeanDefinitionReader解析xml配置

Spring4.3.x 淺析xml配置的解析過程(1)——使用XmlBeanDefinitionReader解析xml配置

概述

Spring預設的BeanFactory是DefaultListableBeanFactory類,spring建立DefaultListableBeanFactory物件後,會把配置資訊轉換成一個一個的BeanDefinition物件,並把這些BeanDefinition物件註冊到DefaultListableBeanFactory物件中,以供bean工廠建立bean例項。BeanDefinition物件儲存的是單個bean的配置資訊,比如依賴類、scope、是否延遲載入等等。

Spring可以通過4種方式配置bean,其一是基於xml的配置,其二種是基於xml+註解的配置,其三是基於java+註解的配置,其四是基於property檔案的配置。前兩種的配置資訊使用XmlBeanDefinitionReader物件來解析;第三種的配置資訊使用AnnotatedBeanDefinitionReader物件來解析;最後一種的配置資訊使用PropertiesBeanDefinitionReader物件來解析。

而這裡要討論的主題是xml配置的解析過程,因此我們的戰場在XmlBeanDefinitionReader,首先看看它的繼承結構,如下圖。

這裡寫圖片描述

簡單的說XmlBeanDefinitionReader實現了BeanDefinitionReader介面,BeanDefinitionReader的設計用意是載入BeanDefintion物件,下面是它載入BeanDefintion的4個介面方法。

    /**
     * 從單個指定的資源物件中載入BeanDefintion,並返回載入的BeanDefintion個數
     */
    int loadBeanDefinitions(Resource resource) throws
BeanDefinitionStoreException; /** * 從多個指定的資源物件中載入BeanDefintion,並返回載入的BeanDefintion個數 */ int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException; /** * 從單個指定的資源地址中載入BeanDefintion,並返回載入的BeanDefintion個數。 * 如果資源載入器是ResourcePatternResolver物件,那麼location引數可以使用萬用字元。 */
int loadBeanDefinitions(String location) throws BeanDefinitionStoreException; /** * 從多個指定的資源地址中載入BeanDefintion,並返回載入的BeanDefintion個數。 */ int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException;

這4個介面在後面會一一談到,這裡還有個問題留給大家,既然是載入BeanDefintion,為什麼不是返回所有載入的BeanDefintion物件,而是返回載入的個數?

在spring中直接使用XmlBeanDefinitionReader的容器有XmlWebApplicationContext、ClassPathXmlApplicationContext、FileSystemXmlApplicationContext。下面就從XmlWebApplicationContext為例來探索xml配置的解析過程。

XmlWebApplicationContext解析配置檔案分為以下2個過程:

1) 建立並初始化XmlBeanDefinitionReader 物件。

2)使用XmlBeanDefinitionReader 物件提供的介面方法來載入BeanDefinition物件。

下面我們通過spring原始碼來探討這個2個過程。

1 建立並初始化XmlBeanDefinitionReader 物件

XmlWebApplicationContext在建立完BeanFacotry的時候會呼叫它的loadBeanDefinitions(DefaultListableBeanFactory beanFactory)方法來載入BeanDefinition,loadBeanDefinitions方法的程式碼如下。

@Override
    protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
        // 根據給定的bean工廠建立新的XmlBeanDefinitionReader物件
        XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

        // 使XmlBeanDefinitionReader物件與上下文物件在同一個資源環境中
        beanDefinitionReader.setEnvironment(this.getEnvironment());
        // 使用上下文物件為XmlBeanDefinitionReader物件的資源載入器
        beanDefinitionReader.setResourceLoader(this);
        // 設定EntityResolver物件,用於載入XML的xsd或者dtd檔案
        beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

        // 鉤子方法,允許子類在載入bean definition之前進一步設定XmlBeanDefinitionReader
        // ->比如,更改XmlBeanDefinitionReader自己提供的DocumentLoader
        // 或者BeanDefinitionDocumentReader等預設物件
        initBeanDefinitionReader(beanDefinitionReader);

        // 使用BeanDefinitionReader載入所有的BeanDefinition物件,見下面的程式碼
        loadBeanDefinitions(beanDefinitionReader);
    }

這段程式碼是為使用BeanDefinitionReader物件載入BeanDefinitioin物件做準備工作。BeanDefinitionRegistry物件是BeanDefinition物件的登錄檔,並且它是XmlBeanDefinitionReader物件建立時必須提供的,除此之外,其它的都使用預設或者由使用者提供。在XmlWebApplicationContext容器裡,容器向BeanDefinitionReader提供了3個物件,第一個是資源環境Environment物件,它可用於判斷beans標籤的profile屬性,後面會談到;第二個是資源載入器ResourceLoader物件;最後一個是用於載入XML驗證檔案(dtd或者xsd檔案)的EntityResolver物件。如果這些還不夠,則可以擴充套件XmlWebApplicationContext容器,並重寫initBeanDefinitionReader方法對BeanDefinitionReader做更多的初始化。

2 使用XmlBeanDefinitionReader 物件來載入BeanDefinition

首先,還是先預覽一下XmlBeanDefinitionReader 載入BeanDefinition的流程:
這裡寫圖片描述

上圖只是大致描述了XmlBeanDefinitionReader物件載入BeanDefinition的過程,還有些重點的旁枝末節沒有展現出來,但這並不影響我們瞭解XmlBeanDefinitionReader解析xml配置的過程。下面我們看看XmlWebApplicationContext如何使用XmlBeanDefinitionReader物件,以及XmlBeanDefinitionReader物件如何按照這個流程執行的。

建立完成BeanDefinitionReader物件後,XmlWebApplicationContext呼叫它的loadBeanDefinitions(XmlBeanDefinitionReader reader)方法來使用BeanDefinitionReader物件。下面是loadBeanDefinitions(XmlBeanDefinitionReader reader)方法的程式碼。

    /**
    * 使用XmlBeanDefinitionReader載入所有的BeanDefinition物件
    **/
    protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {
        // 獲取容器需要載入的配置檔案的地址
        String[] configLocations = getConfigLocations();
        if (configLocations != null) {
            for (String configLocation : configLocations) {
                // 使用XmlBeanDefinitionReader一個一個的讀取spring配置檔案
                reader.loadBeanDefinitions(configLocation);
            }
        }
    }

這部分程式碼是XmlBeanDefinitionReader 使用xml配置檔案地址載入BeanDefinition物件的入口。這裡所呼叫XmlBeanDefinitionReader的loadBeanDefinitions方法繼承自XmlBeanDefinitionReader的父類AbstractBeanDefinitionReader。下面程式碼是loadBeanDefinitions方法在AbstractBeanDefinitionReader類中的實現。

    public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
        return loadBeanDefinitions(location, null);
    }
    public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
        ResourceLoader resourceLoader = getResourceLoader();
        if (resourceLoader == null) {
            throw new BeanDefinitionStoreException(
                    "Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
        }

        if (resourceLoader instanceof ResourcePatternResolver) {
            // 使用資源模式解析器解析配置檔案的路徑並載入資源
            try {
                // 載入所有與指定location引數匹配的所有資源
                Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
                // 載入指定的資源中的所有BeanDefinition
                int loadCount = loadBeanDefinitions(resources);
                if (actualResources != null) {
                    for (Resource resource : resources) {
                        actualResources.add(resource);
                    }
                }
                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 = resourceLoader.getResource(location);
            // 載入指定的resource中的所有BeanDefinition
            int loadCount = loadBeanDefinitions(resource);

            if (actualResources != null) {
                actualResources.add(resource);
            }
            if (logger.isDebugEnabled()) {
                logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]");
            }
            return loadCount;
        }
    }   

這段程式碼主要做的事情是,使用BeanDefinitionReader物件所持有的ResourceLoader來生成Resource物件。然後呼叫BeanDefinitionReader的loadBeanDefinitions(Resource… resources)或者loadBeanDefinitions(Resource resource)方法來執行載入BeanDefinition的程式碼,其中,前一個方法通過多個資原始檔來載入,後一個方法通過一個資原始檔來載入。

首先我們從處理多個Resource物件的loadBeanDefinitions(Resource… resources)方法開始,這個方法已經在AbstractBeanDefinitionReader中有實現,並且XmlBeanDefinitionReader直接繼承了它,程式碼如下。

    public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException {
        Assert.notNull(resources, "Resource array must not be null");
        int counter = 0;
        for (Resource resource : resources) {
            // 一個一個資原始檔的載入BeanDefinition
            // 此介面方法在AbstractBeanDefinitionReader沒有實現
            // XmlWebApplicationContext使用XmlBeanDefinitionReader
            // ->則呼叫XmlBeanDefinitionReader的實現
            counter += loadBeanDefinitions(resource);
        }
        return counter;
    }

這部分程式碼的所做的事情是遍歷傳入的資源Resource物件,並呼叫loadBeanDefinitions(Resource resource)方法載入每一個資源。這個方法在AbstractBeanDefinitionReader並沒有實現,下面是此方法在XmlBeanDefinitionReader類中的實現程式碼。

    public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
        return loadBeanDefinitions(new EncodedResource(resource));
    }
    public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
        Assert.notNull(encodedResource, "EncodedResource must not be null");
        if (logger.isInfoEnabled()) {
            logger.info("Loading XML bean definitions from " + encodedResource.getResource());
        }

        Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
        if (currentResources == null) {
            currentResources = new HashSet<EncodedResource>(4);
            this.resourcesCurrentlyBeingLoaded.set(currentResources);
        }
        if (!currentResources.add(encodedResource)) {
            throw new BeanDefinitionStoreException(
                    "Detected cyclic loading of " + encodedResource + " - check your import definitions!");
        }
        try {
            // 讀取資原始檔輸入流
            InputStream inputStream = encodedResource.getResource().getInputStream();
            try {
                InputSource inputSource = new InputSource(inputStream);
                if (encodedResource.getEncoding() != null) {
                    inputSource.setEncoding(encodedResource.getEncoding());
                }
                // 根據指定的XML檔案載入BeanDefinition
                return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
            }
            finally {
                inputStream.close();
            }
        }
        catch (IOException ex) {
            throw new BeanDefinitionStoreException(
                    "IOException parsing XML document from " + encodedResource.getResource(), ex);
        }
        finally {
            currentResources.remove(encodedResource);
            if (currentResources.isEmpty()) {
                this.resourcesCurrentlyBeingLoaded.remove();
            }
        }
    }

loadBeanDefinitions(EncodedResource encodedResource)方法主要做的事情是從Resource物件中獲取xml檔案輸入流,並用它來建立InputSource物件。然後呼叫XmlBeanDefinitionReader的doLoadBeanDefinitions(InputSource inputSource, Resource resource)方法,程式碼如下。

    protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
            throws BeanDefinitionStoreException {
        try {
            // 載入Document物件
            Document doc = doLoadDocument(inputSource, resource);
            // 註冊BeanDefinition
            return registerBeanDefinitions(doc, resource);
        } catch (BeanDefinitionStoreException ex) {
            throw ex;
        } catch (SAXParseException ex) {
            throw new XmlBeanDefinitionStoreException(resource.getDescription(),
                    "Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex);
        } catch (SAXException ex) {
            throw new XmlBeanDefinitionStoreException(resource.getDescription(),
                    "XML document from " + resource + " is invalid", ex);
        } catch (ParserConfigurationException ex) {
            throw new BeanDefinitionStoreException(resource.getDescription(),
                    "Parser configuration exception parsing XML from " + resource, ex);
        } catch (IOException ex) {
            throw new BeanDefinitionStoreException(resource.getDescription(),
                    "IOException parsing XML document from " + resource, ex);
        } catch (Throwable ex) {
            throw new BeanDefinitionStoreException(resource.getDescription(),
                    "Unexpected exception parsing XML document from " + resource, ex);
        }
    }
    /**
    * 獲取Document物件
    **/
    protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
        // 使用DocumentLoader來載入Document物件
        return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler, getValidationModeForResource(resource), isNamespaceAware());
    }

    /**
    * 獲得XML驗證模式,預設使用xsd
    */
    protected int getValidationModeForResource(Resource resource) {
        int validationModeToUse = getValidationMode();
        if (validationModeToUse != VALIDATION_AUTO) {
            return validationModeToUse;
        }
        int detectedMode = detectValidationMode(resource);
        if (detectedMode != VALIDATION_AUTO) {
            return detectedMode;
        }

        return VALIDATION_XSD;
    }

這部分程式碼所做的事情首先是獲取xml文件的驗證模式,spring使用的xsd模式;然後呼叫DocumentLoader的loadDocument來讀取InputSource物件中的XML內容並建立Document物件,XmlBeanDefinitionReader預設的DocumentLoader為DefaultDocumentLoader;最後呼叫registerBeanDefinitions(Document doc, Resource resource)方法來處理剛建立的Document物件,下面是XmlBeanDefinitionReader類中registerBeanDefinitions方法的程式碼。

    public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
        // 建立BeanDefinitionDocumentReader物件
        // 預設為DefaultBeanDefinitionDocumentReader
        BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
        // documentReader需要持有當前的環境物件
        documentReader.setEnvironment(this.getEnvironment());
        int countBefore = getRegistry().getBeanDefinitionCount();

        //首先建立XmlReaderContext物件
        // 通過BeanDefinitionDocumentReader註冊BeanDefinition
        documentReader.registerBeanDefinitions(doc, createReaderContext(resource));

        return getRegistry().getBeanDefinitionCount() - countBefore;
    }

從這部分程式碼可以看出,XmlBeanDefinitionReader使用BeanDefinitionDocumentReader 物件來載入Document物件中的配置資訊。

這部分程式碼主要做的事情有3步。第一步是建立BeanDefinitionDocumentReader物件,預設是DefaultBeanDefinitionDocumentReader;第二步是建立呼叫它的registerBeanDefinitions方法所需要的XmlReaderContext上下文物件,XmlReaderContext物件持有當前要讀取的資源、xml名稱空間處理;第三步是呼叫documentReader的registerBeanDefinitions(Document doc, XmlReaderContext readerContext)處理Document物件。下面分別探討這三步的程式碼。

第一步建立BeanDefinitionDocumentReader物件。

    /**
    * 建立BeanDefinitionDocumentReader物件
    **/
    protected BeanDefinitionDocumentReader createBeanDefinitionDocumentReader() {
        return BeanDefinitionDocumentReader.class.cast(BeanUtils.instantiateClass(this.documentReaderClass));
    }

第二步:建立XmlReaderContext上下文物件。


    /**
    * 建立XmlReaderContext物件
    **/
    public XmlReaderContext createReaderContext(Resource resource) {
        return new XmlReaderContext(resource, this.problemReporter, this.eventListener,
                this.sourceExtractor, this, getNamespaceHandlerResolver());
    }

    /**
    * 建立NamespaceHandlerResolver物件
    **/
    public NamespaceHandlerResolver getNamespaceHandlerResolver() {
        if (this.namespaceHandlerResolver == null) {
            this.namespaceHandlerResolver = createDefaultNamespaceHandlerResolver();
        }
        return this.namespaceHandlerResolver;
    }

    /**
    * 建立預設的名稱空間處理器容器
    **/
    protected NamespaceHandlerResolver createDefaultNamespaceHandlerResolver() {
        return new DefaultNamespaceHandlerResolver(getResourceLoader().getClassLoader());
    }

第三步執行DefaultBeanDefinitionDocumentReader的registerBeanDefinitions(Document doc, XmlReaderContext readerContext)方法。

    public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
        this.readerContext = readerContext;
        logger.debug("Loading bean definitions");
        // 獲得root節點
        Element root = doc.getDocumentElement();
        // 註冊root節點中的所有BeanDefinition
        doRegisterBeanDefinitions(root);
    }

這一步是處理Document物件的重點也是入口。XML檔案中只允許有一個根節點,上面的程式碼所做的事情就是儲存XmlReaderContext 物件並提取根節點,然後呼叫DefaultBeanDefinitionDocumentReader的doRegisterBeanDefinitions(Element root)方法,這個方法的程式碼如下。

    protected void doRegisterBeanDefinitions(Element root) {

        // 在這個方法中,遞迴所有巢狀<beans>元素。
        // 為了正確地延用並儲存<beans>元素的default-*屬性
        // ->需要把父節點<beans>的delegate記錄下來,也許這個delegate可能為null。
        BeanDefinitionParserDelegate parent = this.delegate;
        // 為當前的<beans>節點建立delgate物件
        this.delegate = createDelegate(getReaderContext(), root, parent);

        if (this.delegate.isDefaultNamespace(root)) {
            // 檢查beans標籤上的profile屬性
            // 宣告:public static final String PROFILE_ATTRIBUTE = "profile";
            String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
            if (StringUtils.hasText(profileSpec)) {
                String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
                        profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
                if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
                    if (logger.isInfoEnabled()) {
                        logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec +
                                "] not matching: " + getReaderContext().getResource());
                    }
                    return;
                }
            }
        }

        preProcessXml(root);
        // 解析根節點
        parseBeanDefinitions(root, this.delegate);
        postProcessXml(root);

        // 解析完巢狀的<beans>標籤後,還原父節點的delegate
        this.delegate = parent;
    }

這段程式碼主要也分成3步,第一步建立BeanDefinitionParserDelegate物件,這個物件的作用是代理DefaultBeanDefinitionDocumentReader解析BeanDefinition物件;第二步檢查bean標籤的profile屬性值是否與環境的匹配,如果不匹配則不處理而直接返回;第三步執行parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate)方法。下面分別探討第一步和第三步。

(1) 建立BeanDefinitionParserDelegate物件,程式碼如下

    protected BeanDefinitionParserDelegate createDelegate(
            XmlReaderContext readerContext, Element root, BeanDefinitionParserDelegate parentDelegate) {

        // 建立BeanDefinitionParserDelegate 
        BeanDefinitionParserDelegate delegate = new BeanDefinitionParserDelegate(readerContext);

        // 初始化delegate的Defaults
        // 如果當前<beans>節點的屬性值等於預設值,則使用父節點<beans>對應的屬性值。
        delegate.initDefaults(root, parentDelegate);
        return delegate;
    }

(2) 執行DefaultBeanDefinitionDocumentReader的parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate)方法,程式碼如下

    protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
        // 檢查root節點的名稱空間是否為預設名稱空間
        // spring配置檔案中預設的名稱空間為"http://www.springframework.org/schema/beans"
        if (delegate.isDefaultNamespace(root)) {
            NodeList nl = root.getChildNodes();
            // 遍歷root節點下的所有子節點
            for (int i = 0; i < nl.getLength(); i++) {
                Node node = nl.item(i);
                if (node instanceof Element) {
                    Element ele = (Element) node;
                    // 檢查子節點的名稱空間是否為預設名稱空間
                    if (delegate.isDefaultNamespace(ele)) {
                        // 解析預設名稱空間的元素節點
                        parseDefaultElement(ele, delegate);
                    } else {
                        // 解析自定義元素節點
                        delegate.parseCustomElement(ele);
                    }
                }
            }
        } else {
            // 解析自定義元素節點
            delegate.parseCustomElement(root);
        }
    }

這段程式碼主要是區分節點的名稱空間,根據不同名稱空間,呼叫相應的方法。如果節點在預設名稱空間(http://www.springframework.org/schema/beans),則呼叫DefaultBeanDefinitionDocumentReader的parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate)方法,否則呼叫BeanDefinitionParserDelegate 的parseCustomElement(Element ele)方法。

(1)處理自定義名稱空間下的節點。執行BeanDefinitionParserDelegate的 parseCustomElement(Element ele) 方法,程式碼如下

    public BeanDefinition parseCustomElement(Element ele) {
        return parseCustomElement(ele, null);
    }
    public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
        String namespaceUri = getNamespaceURI(ele);
        NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
        if (handler == null) {
            error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
            return null;
        }
        return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
    }

這部分程式碼主要是獲取自定義名稱空間的處理器,然後執行處理器的parse方法來建立一個BeanDefinition物件。

(2)處理預設名稱空間下的節點。 執行DefaultBeanDefinitionDocumentReader的parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate)方法,程式碼如下。

    private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
        if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
            // 處理import節點元素
            // 這裡同樣是獲取配置檔案並把註冊BeanDefinition物件
            importBeanDefinitionResource(ele);
        }
        else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
            // 處理alias節點元素
            processAliasRegistration(ele);
        }
        else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
            // 處理bean節點元素
            processBeanDefinition(ele, delegate);
        }
        else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
            // 處理beans節點元素,遞迴呼叫doRegisterBeanDefinitions,詳見4.3
            doRegisterBeanDefinitions(ele);
        }
    }

這段程式碼是處理import、beans、alias、bean標籤的入口方法。

  • import標籤是引入其它spring配置檔案;
  • beans標籤是對bean進行分類配置,比如用一個beans來管理測試環境的bean,用另一個beans來管理生產環境的bean;
  • alias標籤是為一個已定義了的bean取別名,它的name屬性值是bean的id,alias屬性值是要取的別名,多個別名用英文逗號、分號或者空格隔開;
  • bean標籤的資訊就是spring要例項化的物件。

不管是什麼標籤,只有利用bean標籤才會生成BeanDefinition物件,下面重點探討的bean標籤處理。 執行DefaultBeanDefinitionDocumentReader的processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate)方法,程式碼如下。

    /**
    * 解析bean節點,並註冊BeanDefinition物件
    */
    protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {

        // 建立BeanDefinitionHolder
        BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
        if (bdHolder != null) {
            // 裝飾BeanDefinition
            bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
            try {
                // 註冊已經建立好的BeanDefintion
                BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
            } catch (BeanDefinitionStoreException ex) {
                getReaderContext().error("Failed to register bean definition with name '" +
                        bdHolder.getBeanName() + "'", ele, ex);
            }
            // 傳送BeanDefinition註冊事件
            getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
        }
    }

這段程式碼分成三步。第一步,根據傳入的Element物件(bean標籤的)呼叫代理物件的parseBeanDefinitionElement(Element ele)方法建立BeanDefinitionHolder 物件,這個物件持有建立好的BeanDefinition物件、bean的id和bean的別名。

第二步,呼叫代理物件的decorateBeanDefinitionIfRequired(Element ele, BeanDefinitionHolder definitionHolder)來對BeanDefinition物件再加工,主要是解析bean標籤中自定義屬性和自定義標籤。

第三步,呼叫工具類BeanDefinitionReaderUtils的registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)方法,這個方法用於註冊建立好的BeanDefinition。

前面兩步主要是完成BeanDefintion的建立和自定義,在這裡不做更深入的探討,下面我們看看第三步,註冊BeanDefinition物件。 執行BeanDefinitionReaderUtils的registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)方法,把BeanDefinition物件註冊到BeanDefinitionRegistry 物件中

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

        String beanName = definitionHolder.getBeanName();
        // 把BeanDefinition物件註冊到BeanDefinitionRegistry 物件中
        // 我們通過DefaultListableBeanFactory為例,介紹BeanDefinition的註冊
        registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());

        // 把bean id與別名關聯起來
        String[] aliases = definitionHolder.getAliases();
        if (aliases != null) {
            for (String aliase : aliases) {
                registry.registerAlias(beanName, aliase);
            }
        }
    }

這部分程式碼的作用是呼叫BeanDefinitionRegistry 的registerBeanDefinition(String beanName, BeanDefinition beanDefinition)方法來註冊BeanDefinition,然後呼叫它的registerAlias(String name, String alias)來使bean的id和別名關聯起來。

DefaultListableBeanFactory實現了BeanDefinitionRegistry 介面的registerBeanDefinition(String beanName, BeanDefinition beanDefinition)方法,程式碼如下。


    //---------------------------------------------------------------------
    // Implementation of BeanDefinitionRegistry interface
    //---------------------------------------------------------------------

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

        Assert.hasText(beanName, "Bean name must not be empty");
        Assert.notNull(beanDefinition, "BeanDefinition must not be null");

        if (beanDefinition instanceof AbstractBeanDefinition) {
            try {
                ((AbstractBeanDefinition) beanDefinition).validate();
            } catch (BeanDefinitionValidationException ex) {
                throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
                        "Validation of bean definition failed", ex);
            }
        }

        BeanDefinition oldBeanDefinition;

        oldBeanDefinition = this.beanDefinitionMap.get(beanName);
        if (oldBeanDefinition != null) {
            if (!isAllowBeanDefinitionOverriding()) {
                throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
                        "Cannot register bean definition [" + beanDefinition + "] for bean '" + beanName +
                        "': There is already [" + oldBeanDefinition + "] bound.");
            } else if (oldBeanDefinition.getRole() < beanDefinition.getRole()) {
                // e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
                if (this.logger.isWarnEnabled()) {
                    this.logger.warn("Overriding user-defined bean definition for bean '" + beanName +
                            "' with a framework-generated bean definition: replacing [" +
                            oldBeanDefinition + "] with [" + beanDefinition + "]");
                }
            } else if (!beanDefinition.equals(oldBeanDefinition)) {
                if (this.logger.isInfoEnabled()) {
                    this.logger.info("Overriding bean definition for bean '" + beanName +
                            "' with a different definition: replacing [" + oldBeanDefinition +
                            "] with [" + beanDefinition + "]");
                }
            } else {
                if (this.logger.isDebugEnabled()) {
                    this.logger.debug("Overriding bean definition for bean '" + beanName +
                            "' with an equivalent definition: replacing [" + oldBeanDefinition +
                            "] with [" + beanDefinition + "]");
                }
            }
            // 覆蓋原來的BeanDefinition
            this.beanDefinitionMap.put(beanName, beanDefinition);
        } else {
            if (hasBeanCreationStarted()) {
                // BeanDefintion已經註冊完成,而其他bean正在建立
                synchronized (this.beanDefinitionMap) {
                    this.beanDefinitionMap.put(beanName, beanDefinition);
                    List<String> updatedDefinitions = new ArrayList<String>(this.beanDefinitionNames.size() + 1);
                    updatedDefinitions.addAll(this.beanDefinitionNames);
                    updatedDefinitions.add(beanName);
                    this.beanDefinitionNames = updatedDefinitions;
                    if (this.manualSingletonNames.contains(beanName)) {
                        Set<String> updatedSingletons = new LinkedHashSet<String>(this.manualSingletonNames);
                        updatedSingletons.remove(beanName);
                        this.manualSingletonNames = updatedSingletons;
                    }
                }
            } else {
                // 註冊BeanDefintion還未完成
                this.beanDefinitionMap.put(beanName, beanDefinition);
                this.beanDefinitionNames.add(beanName);
                this.manualSingletonNames.remove(beanName);
            }
            this.frozenBeanDefinitionNames = null;
        }

        if (oldBeanDefinition != null || containsSingleton(beanName)) {
            // 重置BeanDefintiion
            resetBeanDefinition(beanName);
        }
    }

這段程式碼的主要作用是DefaultListableBeanFactory把建立完成的BeanDefinition儲存到Map物件beanDefinitionMap中,同時還要把以前已經建立好的bean(如果存在)銷燬掉。

總結

1. XmlBeanDefinitionReader的真正身份

XmlBeanDefinitionReader並不是xml配置的真正解析者,它只是相當於一個指揮官。當它收到一條需要載入BeanDefinition物件的任務後,它只會協調手下去完成相應的工作,它的手下有:

  • ResourceLoader,它把指定的配置檔案地址封裝成Resource物件。

  • DocumentLoader,它把Resource物件中的XML檔案內容轉換為Document物件。預設使用DocumentLoader的實現類DefaultDocumentLoader來載入Document物件。

  • BeanDefinitionDocumentReader,它把Document物件中包含的配置資訊轉換成BeanDefinition物件並把它註冊到BeanDefintionRegistry物件中。預設使用DefaultBeanDefinitionDocumentReader來操作Document物件。在DefaultBeanDefinitionDocumentReader的實現中,它的責任是遍歷xml根節點下的子節點,並把處理bean標籤和自定義名稱空間的標籤(比如aop:,context:,p:等)的細節委託給BeanDefinitionParserDelegate物件,BeanDefinitionParserDelegate才是真正解析配置檔案的地方。

  • NamespaceHandlerResolver,用於獲取非預設名稱空間的處理器,預設是DefaultNamespaceHandlerResolver物件。它雖然由XmlBeanDefinitionReader提供,但真正的使用者是BeanDefinitionParserDelegate類。

2. 更改XmlBeanDefinitionReader的預設物件

比如需要更改DocumentLoader、NamespaceHandlerResolver或者BeanDefinitionDocumentReader,甚至是ResourceLoader。

繼承XmlWebApplicationContext,並重寫initBeanDefinitionReader(XmlBeanDefinitionReader beanDefinitionReader),這個方法在XmlWebApplicationContext只是一個空實現,特意留給子類在使用XmlBeanDefinitionReader物件載入BeanDefinition之前對這個物件進行定製的。

遺留問題

這一篇文章只是大致的過了一下XmlBeanDefinitionReader如何根據指定的配置檔案地址來載入並註冊BeanDefintion物件,至於以下細節並沒有過多的描述。

  1. ResourceLoader如何根據指定的location生成Resource物件。

  2. DocumentLoader如何根據xml輸入流生成為Document物件。

  3. Spring如何解析bean標籤的屬性以及子節點。

  4. Spring如何解析非預設名稱空間的配置。

  5. Spring如何解析註解配置。