Spring源码学习之路---IOC容器初始化要义之bean定义载入(五)

原文地址:https://blog.csdn.net/zuoxiaolong8810/article/details/8656910

          最近工作很忙,时间不多,研究spring的进度被严重拖下来,不过我会一直坚持写完。

            上章说到要带各位去看看bean定义载入的要义,其实就是loadBeanDefinitions这个方法的具体实现步骤,下面我们跟随这个方法去看下它到底是如何载入bean定义的。

            Spring源码学习之路---IOC容器初始化要义之bean定义载入(五)

           上面是我截取的实现了loadBeanDefinitions的类级别截图,loadBeanDefinitions方法是AbstractRefreshableApplicationContext抽象类的模板方法,而此次我们研究的FileSystemXmlApplicationContext中的loadBeanDefinitions方法是由AbstractXmlApplicationContext抽象类实现的。

[java] view plain copy
  1. protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {  
  2.     // Create a new XmlBeanDefinitionReader for the given BeanFactory.  
  3.     XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);  
  4.   
  5.     // Configure the bean definition reader with this context's  
  6.     // resource loading environment.  
  7.     beanDefinitionReader.setResourceLoader(this);  
  8.     beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));  
  9.   
  10.     // Allow a subclass to provide custom initialization of the reader,  
  11.     // then proceed with actually loading the bean definitions.  
  12.     initBeanDefinitionReader(beanDefinitionReader);  
  13.     loadBeanDefinitions(beanDefinitionReader);  
  14. }  

           第一行首先定义了一个reader,很明显,这个就是spring为读取XML配置文件而定制的读取工具,这里AbstractXmlApplicationContext间接实现了ResourceLoader接口,所以该方法的第二行才得以成立,最后一行便是真正载入bean定义的过程。我们追踪其根源,可以发现最终的读取过程正是由reader完成的,代码如下。

[java] view plain copy
  1. public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {  
  2.     Assert.notNull(encodedResource, "EncodedResource must not be null");  
  3.     if (logger.isInfoEnabled()) {  
  4.         logger.info("Loading XML bean definitions from " + encodedResource.getResource());  
  5.     }  
  6.   
  7.     Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();  
  8.     if (currentResources == null) {  
  9.         currentResources = new HashSet<EncodedResource>(4);  
  10.         this.resourcesCurrentlyBeingLoaded.set(currentResources);  
  11.     }  
  12.     if (!currentResources.add(encodedResource)) {  
  13.         throw new BeanDefinitionStoreException(  
  14.                 "Detected cyclic loading of " + encodedResource + " - check your import definitions!");  
  15.     }  
  16.     try {  
  17.         InputStream inputStream = encodedResource.getResource().getInputStream();  
  18.         try {  
  19.             InputSource inputSource = new InputSource(inputStream);  
  20.             if (encodedResource.getEncoding() != null) {  
  21.                 inputSource.setEncoding(encodedResource.getEncoding());  
  22.             }  
  23.             return doLoadBeanDefinitions(inputSource, encodedResource.getResource());  
  24.         }  
  25.         finally {  
  26.             inputStream.close();  
  27.         }  
  28.     }  
  29.     catch (IOException ex) {  
  30.         throw new BeanDefinitionStoreException(  
  31.                 "IOException parsing XML document from " + encodedResource.getResource(), ex);  
  32.     }  
  33.     finally {  
  34.         currentResources.remove(encodedResource);  
  35.         if (currentResources.isEmpty()) {  
  36.             this.resourcesCurrentlyBeingLoaded.remove();  
  37.         }  
  38.     }  
  39. }  

            这个方法中不难发现,try块中的代码才是载入bean定义的真正过程,我们一步一步的扒开bean定义的载入,spring将资源返回的输入流包装以后传给了doLoadBeanDefinitions方法,我们进去看看发生了什么。

[java] view plain copy
  1. protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)  
  2.             throws BeanDefinitionStoreException {  
  3.         try {  
  4.             int validationMode = getValidationModeForResource(resource);  
  5.             Document doc = this.documentLoader.loadDocument(  
  6.                     inputSource, getEntityResolver(), this.errorHandler, validationMode, isNamespaceAware());  
  7.             return registerBeanDefinitions(doc, resource);  
  8.         }  
  9.         catch (BeanDefinitionStoreException ex) {  
  10.             throw ex;  
  11.         }  
  12.         catch (SAXParseException ex) {  
  13.             throw new XmlBeanDefinitionStoreException(resource.getDescription(),  
  14.                     "Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex);  
  15.         }  
  16.         catch (SAXException ex) {  
  17.             throw new XmlBeanDefinitionStoreException(resource.getDescription(),  
  18.                     "XML document from " + resource + " is invalid", ex);  
  19.         }  
  20.         catch (ParserConfigurationException ex) {  
  21.             throw new BeanDefinitionStoreException(resource.getDescription(),  
  22.                     "Parser configuration exception parsing XML from " + resource, ex);  
  23.         }  
  24.         catch (IOException ex) {  
  25.             throw new BeanDefinitionStoreException(resource.getDescription(),  
  26.                     "IOException parsing XML document from " + resource, ex);  
  27.         }  
  28.         catch (Throwable ex) {  
  29.             throw new BeanDefinitionStoreException(resource.getDescription(),  
  30.                     "Unexpected exception parsing XML document from " + resource, ex);  
  31.         }  
  32.     }  

                  可以看到,spring采用documentLoader将资源转换成了Document接口,这正是我们熟知的SAX对XML解析的重要接口之一,这下不难理解了,可以想象出spring一定是根据XSD文件规定的XML格式,解析了XML文件中的各个节点以及属性。尽管如此,我们还是跟着registerBeanDefinitions方法进去看看。此处该方法不再贴出代码,请各位自己跟踪进去看,这个方法里记录了一共注册了多少个bean定义。最终能看出端倪的地方在DefaultBeanDefinitionDocumentReader的parseBeanDefinitions方法中,如下代码。

[java] view plain copy
  1. protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {  
  2.         if (delegate.isDefaultNamespace(root)) {  
  3.             NodeList nl = root.getChildNodes();  
  4.             for (int i = 0; i < nl.getLength(); i++) {  
  5.                 Node node = nl.item(i);  
  6.                 if (node instanceof Element) {  
  7.                     Element ele = (Element) node;  
  8.                     if (delegate.isDefaultNamespace(ele)) {  
  9.                         parseDefaultElement(ele, delegate);  
  10.                     }  
  11.                     else {  
  12.                         delegate.parseCustomElement(ele);  
  13.                     }  
  14.                 }  
  15.             }  
  16.         }  
  17.         else {  
  18.             delegate.parseCustomElement(root);  
  19.         }  
  20.     }  

             这里分了两种解析路线,一个是默认的,一个是自定义的,从这里我们可以看出,我们是可以在spring的配置文件中自定义节点的。

             再往下走就基本上到了spring开始针对具体标签解析的过程,各位如果有兴趣可以自行跟进去看一下spring是如何对XML文件的各个节点和属性进行解析的,了解这个过程可以帮助你熟练的掌握spring中的XML配置文件的各个节点和属性的含义。

            这里我要稍稍总结一下,spring对bean定义的载入有很多种方式,读取的过程是可插拔的,不论何种形式,spring的IOC容器只要获得了bean定义信息,都可以正常工作。而我们熟知的配置读取方式就是XML文件,如果你希望,可以自己定制配置信息的读取过程,有时间我会研究下spring留给我们扩展的接口在哪里。只要找到了这个入口,那么读取配置信息就任由我们宰割了。