1. 程式人生 > >Spring原始碼解析和配置檔案載入

Spring原始碼解析和配置檔案載入

Spring類的繼承結構圖:


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程式碼  收藏程式碼
  1. public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory)  
  2.     throws BeansException  
  3. {  
  4.     super(parentBeanFactory);  
  5.     reader = new XmlBeanDefinitionReader(this);  
  6.     reader.loadBeanDefinitions(resource);  
  7. }  

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

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

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

在BeanDefinitionReader中定義有許多loadBeanDefinitions方法 
Java程式碼  收藏程式碼
  1. public abstract int loadBeanDefinitions(Resource resource)  
  2.     throws BeanDefinitionStoreException;  
  3. public abstract int loadBeanDefinitions(Resource aresource[])  
  4.     throws BeanDefinitionStoreException;  
  5. public abstract int loadBeanDefinitions(String s)  
  6.     throws BeanDefinitionStoreException;  
  7. public abstract int loadBeanDefinitions(String as[])  
  8.     throws BeanDefinitionStoreException;  

來回頭看XmlBeanDefinitionReader對loadBeanDefinitions方法的實現 
在loadBeanDefinitions方法中呼叫了doLoadBeanDefinitions方法, 跟蹤doLoadBeanDefinitions方法 
Java程式碼  收藏程式碼
  1. Document doc = documentLoader.loadDocument(inputSource, getEntityResolver(), errorHandler, validationMode, isNamespaceAware());  

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

跟蹤到DefaultDocumentLoader 
Java程式碼  收藏程式碼
  1. public class DefaultDocumentLoader  
  2.     implements DocumentLoader  
  3. ...  
  4.     public Document loadDocument(InputSource inputSource, EntityResolver entityResolver, ErrorHandler errorHandler, int validationMode, boolean namespaceAware)  
  5.         throws Exception  
  6.     {  
  7.         DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);  
  8.         if(logger.isDebugEnabled())  
  9.             logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]");  
  10.         DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);  
  11.         return builder.parse(inputSource);  
  12.     }  
  13. ...  

哦哦, 我們知道了, 是通過sax解析得到Dom的, 至於怎麼解析, 不屬於Spring範疇, 不做研究. 

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

再繼續, 解析成Dom後接著呼叫了registerBeanDefinitions方法 
Java程式碼  收藏程式碼
  1. return registerBeanDefinitions(doc, resource);  

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

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

Java程式碼  收藏程式碼
  1. public interface BeanDefinitionDocumentReader  

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

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

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

嘿嘿, 開始解析Dom了哦, 其中主要是parseBeanDefinitions方法, 來看看具體是如何解析的 
Java程式碼  收藏程式碼
  1. protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate)  
  2. {  
  3.     if(delegate.isDefaultNamespace(root.getNamespaceURI()))  
  4.     {  
  5.         NodeList nl = root.getChildNodes();  
  6.         for(int i = 0; i < nl.getLength(); i++)  
  7.         {  
  8.             org.w3c.dom.Node node = nl.item(i);  
  9.             if(node instanceof Element)  
  10.             {  
  11.                 Element ele = (Element)node;  
  12.                 String namespaceUri = ele.getNamespaceURI();  
  13.                 if(delegate.isDefaultNamespace(namespaceUri))  
  14.                     parseDefaultElement(ele, delegate);  
  15.                 else  
  16.                     delegate.parseCustomElement(ele);  
  17.             }  
  18.         }  
  19.     } else  
  20.     {  
  21.         delegate.parseCustomElement(root);  
  22.     }  
  23. }  

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

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

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

對<alias>節點, 呼叫processAliasRegistration進行別名解析 
我們主要看對<bean>節點呼叫processBeanDefinition進行解析 
Java程式碼  收藏程式碼
  1. protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate)  
  2. {  
  3.     BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);  
  4.     if(bdHolder != null)  
  5.     {  
  6.         bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);  
  7.         try  
  8.         {