Spring IOC BeanDefinition解析
Spring IOC BeanDefinition解析
IOC(Inversion of Control)即控制反轉,是說創建對象的控制權進行了轉移,以前創建對象的主動權和創建時機是由自己把控的,而現在這種權利轉移到Spring IOC容器。許多非凡的應用,都是由兩個或者多個類通過彼此的合作依賴來實現業務邏輯的,在Spring中,這些依賴關系可以通過把對象的依賴註入交給IOC容器來管理,這樣在解耦代碼的同時提高了代碼的可測試性。
1. 加載bean
加載bean的流程:
(1) 封裝資源文件。當進入XmlBeanDefinitionReader後首先對參數Resource使用EncodedResource類進行封裝。
(2) 獲取輸入流。從Resource中獲取對應的InputStream並構造InputSource。
(3) 通過構造的InputSource實例和Resource實例繼續調用函數doLoadBeanDefinitions。
我們來看一下doLoadBeanDefinitions函數的具體的實現過程(中間省略了loadBeanDefinitions具體方法的一步):
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException { returnloadBeanDefinitions(new EncodedResource(resource)); }
繼續跟進代碼,進入真正的核心處理部分doLoadBeanDefinitions(inputSource, encodedResource.getResource())
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException { try { Document doc= doLoadDocument(inputSource, resource); return registerBeanDefinitions(doc, resource); } catch (BeanDefinitionStoreException ex) { throw ex; } // ……省略異常處理部分 }
在上面冗長的代碼中,假如不考慮異常類的代碼,其實只做了三件事,這三件事的每一件事都必不可少。
(1) 獲取對XML文件的驗證模式。
(2) 加載XML文件,並得到對應的Document。
(3) 根據返回的Document註冊bean信息。
2. 獲取XML的驗證模式
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception { return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler, getValidationModeForResource(resource), isNamespaceAware()); }
2.1驗證模式的讀取
了解XML文件的讀者都應該知道XML文件的驗證模式保證了XML文件的正確性,而比較常用的驗證模式有兩種:DTD和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; } // Hmm, we didn‘t get a clear indication... Let‘s assume XSD, // since apparently no DTD declaration has been found up until // detection stopped (before finding the document‘s root tag). return VALIDATION_XSD; }
方法的實現其實還是很簡單的,無非是如果設定了驗證模式則使用設定的驗證模式,否則使用自動的驗證模式,自檢測驗證模式的功能相對來說比較簡單,這裏就不再多說了。
3. 獲取Document
經過了驗證模式準備的步驟就可以進行Document加載了,同樣XmlBeanDefinitionReader對於文檔的讀取並沒有親力親為,而是委托給了DocumentLoader去執行,解析代碼如下(DefaultDocumentLoader中)
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解析XML文檔的套路大致都差不多,Spring在這裏並沒有什麽特殊的地方,同樣首先創建DocumentBuilderFactory再通過DocumentBuilderFactory創建DocumentBuilder,進而解析inputSource來返回Document對象。
4. 解析及註冊BeanDefinitions
把文件轉換成Document之後,接下來就可以提取及註冊bean了。
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { //使用DefaultBeanDefinitionDocumentReader實例化BeanDefinitionDocumentReader BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); // 在實例化BeanDefinitionReader時候會將BeanDefinitionRegistry傳入,默認使用繼承自DefaultListableBeanFactory的子類 // 記錄統計前BeanDefinition的加載個數 int countBefore = getRegistry().getBeanDefinitionCount(); // 加載及註冊bean documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); // 記錄本次加載的BeanDefinition個數 return getRegistry().getBeanDefinitionCount() - countBefore; }
進入DefaultBeanDefinitionDocumentReader的registerBeanDefinitions方法
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) { this.readerContext = readerContext; logger.debug("Loading bean definitions"); Element root = doc.getDocumentElement(); doRegisterBeanDefinitions(root); }
下面進入核心邏輯的底部doRegisterBeanDefinitions(root)方法
protected void doRegisterBeanDefinitions(Element root) { // 專門處理解析 BeanDefinitionParserDelegate parent = this.delegate; this.delegate = createDelegate(getReaderContext(), root, parent); if (this.delegate.isDefaultNamespace(root)) { // 處理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); this.delegate = parent; }
4.1profile屬性的使用
這個特性可以同時在配置文件中部署兩套配置來適用於生產環境和開發環境,這樣可以方便的進行切換開發、部署環境,最常用的是更換不同的數據庫。
4.2解析並註冊BeanDefinitions
處理了profile後就可以進行XML的讀取了,跟蹤代碼進入parseBeanDefinitions(root, this.delegate)
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) { // 對beans的處理 if (delegate.isDefaultNamespace(root)) { NodeList nl = root.getChildNodes(); 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)) { // 對bean的處理 parseDefaultElement(ele, delegate); } else { // 對bean的處理 delegate.parseCustomElement(ele); } } } } else { delegate.parseCustomElement(root); } }
上面的代碼看起來邏輯還是挺清晰的,因為在spring的xml配置裏面有兩大類bean申明,一個是默認的,一個是自定義的,兩種方式的讀取及解析差別還是非常大的,如果采用Spring默認的配置,spring當然知道該怎麽做,但是如果是自定義的,那麽久需要用戶實現一些接口及配置了。
對於標簽解析,請看我下一篇文章。
Spring IOC BeanDefinition解析