zoukankan      html  css  js  c++  java
  • 容器的基础 XmlBeanFactory(下篇)

    前言

      这篇文章是接着上篇文章写的,如果没有看过上篇文章容器的基础 XmlBeanFactory(上篇)建议先去看一下,上篇文章讲述了加载bean之前的,Spring所要做的准备工作,这篇文章就直接讲述Spring加载bean的过程,话不多说,开始。

    加载Bean

      之前在上一篇文章中讲述到了在XmlBeanFactory构造函数中调用了XMLBeanDefinitionReader类型的reader属性提供的方法:this.reader.loadBeanDefinitions(resource),而这句代码则是整个资源加载的切入点,先来看看这个方法的时序图:

       从上面时序图中可以看出,加载XML文档和解析注册Bean,一直都是在做准备工作。根据上面的时序图我们来分析一下这里究竟在准备上面?

      (1)封装资源文件。当进入XMLBeanDefinitionReader后,首先对参数Resource使用EncodeResource类进行封装。

      (2)获取输入流。从Resource中获取对应的InputStream并构造InputSource。

      (3)通过构造的InputSource实例和Resource实例继续调用函数doLoadBeanDefinitions。我们来看一下loadBeanDefinitions函数的具体实现过程:

      public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
            return loadBeanDefinitions(new EncodedResource(resource));
        }

       那么EncodedResource的作用是什么呢?通过名称,大致上可以推断出这个类的主要用于对资源文件的编码进行处理的。其中最主要的逻辑体现在getReader()方法中,当设置了编码属性的时候Spring会使用相应的编码作为输入流的编码。来看一下getReader()方法的源码:

      public Reader getReader() throws IOException {
            if (this.charset != null) {
                return new InputStreamReader(this.resource.getInputStream(), this.charset);
            }
            else if (this.encoding != null) {
                return new InputStreamReader(this.resource.getInputStream(), this.encoding);
            }
            else {
                return new InputStreamReader(this.resource.getInputStream());
            }
        }

       上面的代码构造了一个有编码(encoding)的InputStreamReader。当构造好的encodeResource对象后,再次转入了可复用方法loadBeanDefinitions(new EncodedResource(resource))。这个方法内部才是真正的数据准备阶段,也就是时序图描述的逻辑,一起来看一下:

     1 public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
     2         Assert.notNull(encodedResource, "EncodedResource must not be null");
     3         if (logger.isTraceEnabled()) {
     4             logger.trace("Loading XML bean definitions from " + encodedResource);
     5         }
     6         //通过属性来记录已经加载的资源
     7         Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
     8         if (currentResources == null) {
     9             currentResources = new HashSet<>(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             //从encodeResource中获取已经封装的Resource资源,并从该资源中获取其中的InputStream
    18             InputStream inputStream = encodedResource.getResource().getInputStream();
    19             try {
    20                 InputSource inputSource = new InputSource(inputStream);
    21                 if (encodedResource.getEncoding() != null) {
    22                     inputSource.setEncoding(encodedResource.getEncoding());
    23                 }
    24                 return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
    25             }
    26             finally {
    27                 //关闭输入流
    28                 inputStream.close();
    29             }
    30         }
    31         catch (IOException ex) {
    32             throw new BeanDefinitionStoreException(
    33                     "IOException parsing XML document from " + encodedResource.getResource(), ex);
    34         }
    35         finally {
    36             currentResources.remove(encodedResource);
    37             if (currentResources.isEmpty()) {
    38                 this.resourcesCurrentlyBeingLoaded.remove();
    39             }
    40         }
    41     }

       SAX:SAX基于事件的解析,解析器在一次读取XML文件中根据读取的数据产生相应的事件,由应用程序实现相应的事件处理逻辑,即它是一种“推”的解析方式;这种解析方法速度快、占用内存少,但是它需要应用程序自己处理解析器的状态,实现起来会比较麻烦。

      再一次来梳理一下数据准备阶段的逻辑,首先对传入的Resource参数进行封装,目的是考虑到Resource可能存在编码要求的情况,其次,通过SAX读取XML文件的方式来准备InputSource对象,最后将准备的数据通过参数传递给真正处理的核心部分doLoadBeanDefinitions(inputSource,encodedREsource.getResource())。来了解一下这个方法:

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

      在上面的代码中,假如不考虑异常类的代码,其实只是做了三件事,这三件事的每一件都必不可少:

      (1)获取对XML文件的验证模式(在doLoadDocument方法里调用getValidationModeForResource(resource)方法)。

      (2)加载XML文件,并得到对应的Document。

      (3)根据返回的Document注册Bean信息。

      这三个步骤支撑着整个Spring容器部分的实现基础,尤其是第三步对配置文件的解析,逻辑非常复杂。

    至此,整个Spring的容器基础,已经讲述完毕。

    参考:《Spring源码深度解析》 郝佳 编著:

    作者:Joe
    努力了的才叫梦想,不努力的就是空想,努力并且坚持下去,毕竟这是我相信的力量
  • 相关阅读:
    使用MobaXterm远程连接Ubuntu,启动Octave,界面不能正常显示
    ABP .Net Core 日志组件集成使用NLog
    ABP .Net Core Entity Framework迁移使用MySql数据库
    ABP前端使用阿里云angular2 UI框架NG-ZORRO分享
    阿里云 Angular 2 UI框架 NG-ZORRO介绍
    Visual Studio 2019 Window Form 本地打包发布猫腻
    VS Code + NWJS(Node-Webkit)0.14.7 + SQLite3 + Angular6 构建跨平台桌面应用
    ABP .Net Core 调用异步方法抛异常A second operation started on this context before a previous asynchronous operation completed
    ABP .Net Core To Json序列化配置
    .Net EF Core数据库使用SQL server 2008 R2分页报错How to avoid the “Incorrect syntax near 'OFFSET'. Invalid usage of the option NEXT in the FETCH statement.”
  • 原文地址:https://www.cnblogs.com/Joe-Go/p/10057391.html
Copyright © 2011-2022 走看看