1. 程式人生 > >XmlBeanDefinitionReader-----Spring原始碼解析 配置檔案裝載與解析

XmlBeanDefinitionReader-----Spring原始碼解析 配置檔案裝載與解析

以下內容有部分摘自網路



步驟A. 讀取 Resource 檔案形成 Document 模型

    類圖: XmlBeanFactory -> XmlBeanDefinitionReader

    Spring 使用 XmlBeanDefinitionReader 來讀取並解析 xml 檔案,XmlBeanDefinitionReader 是 BeanDefinitionReader 介面的實現。
    BeanDefinitionReader 定義了 Spring 讀取 Bean 定義的一個介面,這個介面中有一些 loadBeanDefinitions 方法, 用於讀取 Bean 配置。
    BeanDefinitionReader 介面有兩個具體的實現,其中之一就是從 Xml 檔案中讀取配置的 XmlBeanDefinitionReader,另一個則是從 Java Properties 檔案中讀取配置的PropertiesBeanDefinitionReader。
    (注:開發人員也可以提供自己的 BeanDefinitionReader 實現,根據自己的需要來讀取 spring bean 定義的配置。)

步驟B. 解析 Document 得到 Bean 配置


    類圖: XmlBeanDefinitionReader-> BeanDefinitionDocumentReader

    BeanDefinitionDocumentReader 介面中只定義了一個方法 registerBeanDefinitions. 有一個預設實現 DefaultBeanDefinitionDocumentReader.
    DefaultBeanDefinitionDocumentReader 主要完成兩件事情,解析 <bean> 元素,為擴充套件 spring 的元素尋找合適的解析器,並把相應的元素交給解析器解析。



過程:
    在 XmlBeanFactory 中建立了 XmlBeanDefinitionReader 的例項,並在 XmlBeanFactory 的構造方法中呼叫了 XmlBeanDefinitionReader 的 loadBeanDefinitions 方法,由 loadBeanDefinitions 方法負責載入 bean 配置並把 bean 配置註冊到 XmlBeanFactory 中。
    在 XmlBeanDefinitionReader 的 loadBeanDefinitions 方法中, 呼叫 DefaultDocumentLoader 的 loadDocument 讀取配置檔案為 Document, 然後呼叫 BeanDefinitionDocumentReader 的 registerBeanDefinitions 方法 來解析 Bean.



原始碼解析:

在XmlBeanFactory初始化時, 需要指定Resource物件.
Java程式碼  收藏程式碼
    public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory)  
        throws BeansException  
    {  
        super(parentBeanFactory);  
        reader = new XmlBeanDefinitionReader(this);  
        reader.loadBeanDefinitions(resource);  
    }  


1. 先來分析下XmlBeanDefinitionReader這個類.
Java程式碼  收藏程式碼
public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader  

接著
Java程式碼  收藏程式碼
    public abstract class AbstractBeanDefinitionReader  
        implements BeanDefinitionReader  


再繼續
Java程式碼  收藏程式碼
    public interface BeanDefinitionReader  


在BeanDefinitionReader中定義有許多loadBeanDefinitions方法
Java程式碼  收藏程式碼
    public abstract int loadBeanDefinitions(Resource resource)  
        throws BeanDefinitionStoreException;  
      
    public abstract int loadBeanDefinitions(Resource aresource[])  
        throws BeanDefinitionStoreException;  
      
    public abstract int loadBeanDefinitions(String s)  
        throws BeanDefinitionStoreException;  
      
    public abstract int loadBeanDefinitions(String as[])  
        throws BeanDefinitionStoreException;  


來回頭看XmlBeanDefinitionReader對loadBeanDefinitions方法的實現
在loadBeanDefinitions方法中呼叫了doLoadBeanDefinitions方法, 跟蹤doLoadBeanDefinitions方法
Java程式碼  收藏程式碼


    Document doc = documentLoader.loadDocument(inputSource, getEntityResolver(), errorHandler, validationMode, isNamespaceAware());  

通過一個叫documentLoader的東西的loadDocument方法來載入配置檔案形成DOM, 來看看documentLoader
Java程式碼  收藏程式碼
    private DocumentLoader documentLoader  
    ...  
    documentLoader = new DefaultDocumentLoader();  


跟蹤到DefaultDocumentLoader
Java程式碼  收藏程式碼
    public class DefaultDocumentLoader  
        implements DocumentLoader  
    ...  
        public Document loadDocument(InputSource inputSource, EntityResolver entityResolver, ErrorHandler errorHandler, int validationMode, boolean namespaceAware)  
            throws Exception  
        {  
            DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);  
            if(logger.isDebugEnabled())  
                logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]");  
            DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);  
            return builder.parse(inputSource);  
        }  
    ...  
哦哦, 我們知道了, 是通過sax解析得到Dom的, 至於怎麼解析, 不屬於Spring範疇, 不做研究.

在這一步, 已完成了從配置檔案讀取到Domcument. 接著要開始解析Dom了

再繼續, 解析成Dom後接著呼叫了registerBeanDefinitions方法
Java程式碼  收藏程式碼

        
    return registerBeanDefinitions(doc, resource);  

來看看registerBeanDefinitions的實現
Java程式碼  收藏程式碼
    ...  
    BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();  
    int countBefore = getRegistry().getBeanDefinitionCount();  
    documentReader.registerBeanDefinitions(doc, createReaderContext(resource));  
    return getRegistry().getBeanDefinitionCount() - countBefore;  
    ...  


在這裡, 有一個BeanDefinitionDocumentReader介面, 實際上Spring對它有一個預設的實現類叫DefaultBeanDefinitionDocumentReader, 來看看它的家族
Java程式碼  收藏程式碼

      
    public class DefaultBeanDefinitionDocumentReader   
Java程式碼  收藏程式碼

      
    public interface BeanDefinitionDocumentReader  


BeanDefinitionDocumentReader只有一個registerBeanDefinitions方法
Java程式碼  收藏程式碼
    public abstract void registerBeanDefinitions(Document document, XmlReaderContext xmlreadercontext)  
        throws BeanDefinitionStoreException;  


該方法需要兩個引數, 一個是Document模型,這個應該是我們讀取配置檔案獲取到的, 另一個是XmlReaderContext物件, 我們在上面方法中看到是通過createReaderContext(resource)得到的, 那就看看具體如何得到
Java程式碼  收藏程式碼
    protected XmlReaderContext createReaderContext(Resource resource)  
    {  
        if(namespaceHandlerResolver == null)  
            namespaceHandlerResolver = createDefaultNamespaceHandlerResolver();  
        return new XmlReaderContext(resource, problemReporter, eventListener, sourceExtractor, this, namespaceHandlerResolver);  
    }  


能過建構函式new出來的, 且有一個重要引數resource
再繼續來看DefaultBeanDefinitionDocumentReader對BeanDefinitionDocumentReader的registerBeanDefinitions方法實現
Java程式碼  收藏程式碼
      public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext)  
      {  
          ...  
    BeanDefinitionParserDelegate delegate = createHelper(readerContext, root);  
    ...  
          parseBeanDefinitions(root, delegate);  
          ...  
      }  


嘿嘿, 開始解析Dom了哦, 其中主要是parseBeanDefinitions方法, 來看看具體是如何解析的
Java程式碼  收藏程式碼
    protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate)  
    {  
        if(delegate.isDefaultNamespace(root.getNamespaceURI()))  
        {  
            NodeList nl = root.getChildNodes();  
            for(int i = 0; i < nl.getLength(); i++)  
            {  
                org.w3c.dom.Node node = nl.item(i);  
                if(node instanceof Element)  
                {  
                    Element ele = (Element)node;  
                    String namespaceUri = ele.getNamespaceURI();  
                    if(delegate.isDefaultNamespace(namespaceUri))  
                        parseDefaultElement(ele, delegate);  
                    else  
                        delegate.parseCustomElement(ele);  
                }  
            }  
      
        } else  
        {  
            delegate.parseCustomElement(root);  
        }  
    }  


看到了吧, 迴圈解析Domcument節點
parseDefaultElement方法和delegate的parseCustomElement方法
先來看parseDefaultElement方法
Java程式碼  收藏程式碼
    private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate)  
    {  
        if(DomUtils.nodeNameEquals(ele, "import"))  
            importBeanDefinitionResource(ele);  
        else  
        if(DomUtils.nodeNameEquals(ele, "alias"))  
            processAliasRegistration(ele);  
        else  
        if(DomUtils.nodeNameEquals(ele, "bean"))  
            processBeanDefinition(ele, delegate);  
    }  


看到這就很清楚了, 就是根據節點的名稱作不同解析, 如我們Spring配置檔案中常有以下幾種配置
Java程式碼  收藏程式碼
    <import resource="classpath:xxx" />  
    <bean id="xxx" class="xxx.xxx.xxx" />  
    <alias name="xxxx" alias="yyyyy"/>  


對<import>節點, 呼叫importBeanDefinitionResource方法解析, 此方法中, 又回到第一步讀取配置檔案並解析. 如此遞迴迴圈.
Java程式碼  收藏程式碼
    ...  
    Resource relativeResource = getReaderContext().getResource().createRelative(location);  
    int importCount = getReaderContext().getReader().loadBeanDefinitions(relativeResource);  
    ...  


對<alias>節點, 呼叫processAliasRegistration進行別名解析
我們主要看對<bean>節點呼叫processBeanDefinition進行解析
Java程式碼  收藏程式碼
    protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate)  
    {  
        BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);  
        if(bdHolder != null)  
        {  
            bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);  
            try  
            {  
                BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());  
            }  
            catch(BeanDefinitionStoreException ex)  
            {  
                getReaderContext().error("Failed to register bean definition with name '" + bdHolder.getBeanName() + "'", ele, ex);  
            }  
            getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));  
        }  
    }  
嘿嘿, 又用到delegate物件了, 且呼叫它的BeanDefinitionHolder方法, 返回一個BeanDefinitionHolder, 進去看它的parseBeanDefinitionElement方法
Java程式碼  收藏程式碼
    public class BeanDefinitionParserDelegate  
    {     
        private final Set usedNames = new HashSet();  
        ...  
        public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean)  
        {  
            ... 解析id, name等屬性, 並驗證name是否唯一, 並將name儲存在usedNames中  
            AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);  
            ...   
            return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);  
        }  
        ...  
    }  
可以看到, 在BeanDefinitionHolder中儲存了BeanDefinition的定義
OK, 重頭戲開始, 最經典的部分出現了, 請看parseBeanDefinitionElement方法
Java程式碼  收藏程式碼
    public AbstractBeanDefinition parseBeanDefinitionElement(Element ele, String beanName, BeanDefinition containingBean)  
    {  
        ...  
        程式碼太長, 請參考具體程式碼  
        AbstractBeanDefinition bd = BeanDefinitionReaderUtils.createBeanDefinition(parent, className, readerContext.getBeanClassLoader());  
        ...  
        return abstractbeandefinition;  
        ...  
    }  
在這個方法中, 解析了bean的所有屬性, 有最常用的class, scope, lazy-init等等. 並返回一個AbstractBeanDefinition例項. 至於具體怎麼解析, 就只能進一步跟蹤了, 不過既然到了這一步, 已經明白了它的基本原理, 具體實現就不作介紹

這一步將節點解析成BeanDefinitionHolder物件, 再看看如何註冊, 回到DefaultBeanDefinitionDocumentReader的processBeanDefinition方法
看到對解析到的bdHolder物件又做了decorateBeanDefinitionIfRequired操作, 來看看實現
... 暫留空

接著呼叫了BeanDefinitionReaderUtils的registerBeanDefinition方法註冊bdHolder, 來看看如何實現的
Java程式碼  收藏程式碼
    public class BeanDefinitionReaderUtils  
    {  
        public static void registerBeanDefinition(BeanDefinitionHolder bdHolder, BeanDefinitionRegistry beanFactory)  
            throws BeanDefinitionStoreException  
        {  
            String beanName = bdHolder.getBeanName();  
            beanFactory.registerBeanDefinition(beanName, bdHolder.getBeanDefinition());  
            String aliases[] = bdHolder.getAliases();  
            if(aliases != null)  
            {  
                for(int i = 0; i < aliases.length; i++)  
                    beanFactory.registerAlias(beanName, aliases[i]);  
      
            }  
        }  
    }  


看吧, 又呼叫了BeanDefinitionRegistry的registerBeanDefinition方法, 跟蹤之 (這個要看DefaultListableBeanFactory的實現)
Java程式碼  收藏程式碼
    public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory  
        implements ConfigurableListableBeanFactory, BeanDefinitionRegistry  
    {  
        private final Map beanDefinitionMap;  
        private final List beanDefinitionNames;  
        ...  
        public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)  
            throws BeanDefinitionStoreException  
        {  
            ...  
            Object oldBeanDefinition = beanDefinitionMap.get(beanName);  
            ...  
            beanDefinitionMap.put(beanName, beanDefinition);  
            ...  
        }  
    }  


這裡, 看到了一個最最重要的物件就是beanDefinitionMap, 這個map存放了所有的bean物件, 和我們通常講的容器概念最為接近, getBean時實際是也是從這裡轔取物件, 相同的還有一個beanDefinitionNames, 但這個只儲存bean的名稱
完成上面之後, 還有一步操作beanFactory.registerAlias(beanName, aliases[i]);
這個實現實際是上AbstractBeanFactory抽象類所定義的

是不是特興奮, 已經揭開它神祕的面紗了
定義 -> 定位 -> 裝載 -> 註冊 這幾步已經完成了, 以後繼續看Spring是如何建立bean及例項化的