作者:zuoxiaolong8810(左瀟龍),轉載請注明出處,特別說明:本博文來自博主原博客,為保證新博客中博文的完整性,特復制到此留存,如需轉載請注明新博客地址即可。
最近工作很忙,時間不多,研究spring的進度被嚴重拖下來,不過我會一直堅持寫完。
上章說到要帶各位去看看bean定義載入的要義,其實就是loadBeanDefinitions這個方法的具體實現步驟,下面我們跟隨這個方法去看下它到底是如何載入bean定義的。
上面是我截取的實現了loadBeanDefinitions的類級別截圖,loadBeanDefinitions方法是AbstractRefreshableApplicationContext抽象類的模板方法,而此次我們研究的FileSystemXmlApplicationContext中的loadBeanDefinitions方法是由AbstractXmlApplicationContext抽象類實現的。
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException { // Create a new XmlBeanDefinitionReader for the given BeanFactory. XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory); // Configure the bean definition reader with this context's // resource loading environment. beanDefinitionReader.setResourceLoader(this); beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this)); // Allow a subclass to provide custom initialization of the reader, // then proceed with actually loading the bean definitions. initBeanDefinitionReader(beanDefinitionReader); loadBeanDefinitions(beanDefinitionReader); }
第一行首先定義了一個reader,很明顯,這個就是spring為讀取XML配置文件而定制的讀取工具,這里AbstractXmlApplicationContext間接實現了ResourceLoader接口,所以該方法的第二行才得以成立,最后一行便是真正載入bean定義的過程。我們追蹤其根源,可以發現最終的讀取過程正是由reader完成的,代碼如下。
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()); } 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(); } } }
這個方法中不難發現,try塊中的代碼才是載入bean定義的真正過程,我們一步一步的扒開bean定義的載入,spring將資源返回的輸入流包裝以后傳給了doLoadBeanDefinitions方法,我們進去看看發生了什么。
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException { try { int validationMode = getValidationModeForResource(resource); Document doc = this.documentLoader.loadDocument( inputSource, getEntityResolver(), this.errorHandler, validationMode, isNamespaceAware()); 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); } }
可以看到,spring采用documentLoader將資源轉換成了Document接口,這正是我們熟知的SAX對XML解析的重要接口之一,這下不難理解了,可以想象出spring一定是根據XSD文件規定的XML格式,解析了XML文件中的各個節點以及屬性。盡管如此,我們還是跟着registerBeanDefinitions方法進去看看。此處該方法不再貼出代碼,請各位自己跟蹤進去看,這個方法里記錄了一共注冊了多少個bean定義。最終能看出端倪的地方在DefaultBeanDefinitionDocumentReader的parseBeanDefinitions方法中,如下代碼。
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) { 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)) { parseDefaultElement(ele, delegate); } else { delegate.parseCustomElement(ele); } } } } else { delegate.parseCustomElement(root); } }
這里分了兩種解析路線,一個是默認的,一個是自定義的,從這里我們可以看出,我們是可以在spring的配置文件中自定義節點的。
再往下走就基本上到了spring開始針對具體標簽解析的過程,各位如果有興趣可以自行跟進去看一下spring是如何對XML文件的各個節點和屬性進行解析的,了解這個過程可以幫助你熟練的掌握spring中的XML配置文件的各個節點和屬性的含義。
這里我要稍稍總結一下,spring對bean定義的載入有很多種方式,讀取的過程是可插拔的,不論何種形式,spring的IOC容器只要獲得了bean定義信息,都可以正常工作。而我們熟知的配置讀取方式就是XML文件,如果你希望,可以自己定制配置信息的讀取過程,有時間我會研究下spring留給我們擴展的接口在哪里。只要找到了這個入口,那么讀取配置信息就任由我們宰割了。