美文网首页
1.2.Spring源码解析——容器的基础XmlBeanFact

1.2.Spring源码解析——容器的基础XmlBeanFact

作者: szhlcy | 来源:发表于2018-07-28 11:18 被阅读0次

    1.通过以下代码分析

    BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("Test.xml"));
    

    通过XmlBeanFactory初始化时序图,看看上面代码的执行逻辑


    容器的基础.png

     在测试的BeanFactoryTest中首先调用ClassPathResource的构造函数来构造Resource资源文件的实例对象,后续的资源处理可以用Resource提供的各种服务操作,有了Resource之后就可以进行XmlBeanFactory的初始化了。


    2.配置文件的封装

     在Java中,将不同来源的资源抽象成URL,通过注册不同的handle(URLStreamHandler)来处理不同来源的资源的读取逻辑,一般handle的类型使用不同的前缀(协议,Protocol)来识别,如“file”,“http”等,然而URL没有默认定义相会Classpath或者ServletContext等资源的handle,虽然可以注册自己的Handler来解析但是需要了解URL的实现机制,而且URL也没有提供一些基本的方法,如检查当前资源是不是存在,可读的方法。
    Spring对其内部使用到的资源进行了自己的抽象结构:Resource接口来封装底层资源。

    public interface InputStreamSource {
        InputStream getInputStream() throws IOException;
    }
    
    public interface Resource extends InputStreamSource {
      //是否存在
      boolean exists();
      //是否可读
      boolean isReadable();
      //是否处于打开的状态
      boolean isOpen();
      URL getURL() throws IOException;
      URI getURI() throws IOException;
      File getFile() throws IOException;
      long contentLength() throws IOException;
      long lastModified() throws IOException;
      //根据当前资源创建一个相对资源
      Resource createRelative(String relativePath) throws IOException;'
      String getFilename();
      //在错误处理中的打印信息
      String getDescription();
    }
    

     InputStreamSource封装任何能返回InputStream的类,比如File,Classpath下的资源和Byte Array等。
     Resource接口抽象了所有Spring内部使用到的底层资源:File,URL,ClassPath等。首先,定义了3个用于判断当前资源状态的方法。另外还提供了不同资源到URL,URI,File类型的转换,以及获取lastModified属性,文件名的方法。还提供了基于当前资源创建一个相对资源的方法;createRelative()。
     对于不同来源的资源文件都有相应的Resource实现:罗列部分。


    容器的基础2.png

     对于实现的方式很简单,以getInputStream为例,ClassPathResource中实现的方式是通过class或者classLoader提供的底层方法进行调用,而对于FileSystemResource的实现直接使用FileInputStream对文件进行实例化。

    //ClassPathResource.java中的getInputStream
    public InputStream getInputStream() throws IOException {
       InputStream is;
       if (this.clazz != null) {
          is = this.clazz.getResourceAsStream(this.path);
       }
       else if (this.classLoader != null) {
          is = this.classLoader.getResourceAsStream(this.path);
       }
       else {
          is = ClassLoader.getSystemResourceAsStream(this.path);
       }
       if (is == null) {
          throw new FileNotFoundException(getDescription() + " cannot be opened because it does not exist");
       }
       return is;
    }
    
    FileSystemResource.java 中的getInputStream
    public InputStream getInputStream() throws IOException {
       return new FileInputStream(this.file);
    }
    

    通过Resource相关类完成了对配置文件进行了封装后配置文件的读取工作就全权交给XmlBeanDefinitionReader处理了。


    3.XmlBeanFactory的初始化过程:

    XmlBeanFactory的初始化有若干办法,Spring提供了很多的构造函数,代码如下:

    public class XmlBeanFactory extends DefaultListableBeanFactory {
    //获取XmlBeanDefinitionReader用于加载resource资源用
    private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);
    
    public XmlBeanFactory(Resource resource) throws BeansException {
                 //调用XmlBeanFactory(Resource,BeanFactory)构造方法
                      this(resource, null);
    }
    
    //parentBeanFactory为父类BeanFactory用于factory合并,可以为空
    public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
             super(parentBeanFactory);
            //这里才是加载资源的正真实现
             this.reader.loadBeanDefinitions(resource);
    }
    }
    

     在调用加载资源文件之前还有一个调用父类构造器初始化的过程:super(parentBeanFactory),跟踪代码到父类AbstractAutowireCapableBeanFactory构造器

    public AbstractAutowireCapableBeanFactory() {
       super();
       ignoreDependencyInterface(BeanNameAware.class);
       ignoreDependencyInterface(BeanFactoryAware.class);
       ignoreDependencyInterface(BeanClassLoaderAware.class);
    }
    

     其中ignoreDependencyInterface方法的主要功能是忽略给定接口的自动装备功能,这样做的目的是:
     如果A中有属性B,那么当Spring在获取A的Bean的时候如果其属性B还没有初始化,那么Spring就会自动初始化B,这也是Spring中提供的一个重要特性。
     但是,某些情况下,B不会被初始化,其中的一种情况就是B实现了BeanNameAware接口。Spring中是这样介绍的:自动装配时忽略给定的依赖接口,典型应用就是通过其他方式解析Application上下文注册依赖,类似于BeanFactory通过BeanNameAware进行注册或者ApplicationContext通过ApplicationContextAware进行注入。


    4.加载Bean

    容器的基础3.png

    从 this.reader.loadBeanDefinitions(resource)这行代码作为切入点。
    (1)封装资源文件:当进入XmlBeanDefinitionReader后首先对参数Resource使用EncodedResource类进行封装
    (2)获取输入流。从Resource中获取对应的InputStream并构造InputSource
    (3)通过构造的InputSource实例和Resource实例继续调用函数doLoadBeanDefinition。
    具体的实现过程:

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

    EncodedResource这个类树妖适用于对资源文件的编码进行处理。其中主要逻辑体现在类中的getReader()方法中,当设置了编码属性的时候Spring会使用相应的编码作为输入流的编码

        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());
            }
        }
    

    上面代码构造了一个有编码的inputStreamReader。当构造好encodedResource对象后,再次转入了可服用方法LoadBeanDefinition(new EncodedResource(resource))。
    这个方法内部才是正真的数据准备阶段;

    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 {
                //从encodedResource中获取已经编码封装的Resource对象并再次从Resource中获取inputStream
                InputStream inputStream = encodedResource.getResource().getInputStream();
                try {
                    //InputSource这个类并不是来自Spring,他的全路径时prg.xml.sax.inputStream
                    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();
                }
            }
        }
    

    再次整理一次,首先对传入的resource参数做封装,目的是考虑到Resource可能存在编码要求的情况,其次,通过SAX读取XML文件的方式来准备InputSource对象,最后将准备的数据通过参数传入正真的核心处理部分doLoadBeanDefinitions(inputSource, encodedResource.getResource());

    protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
                throws BeanDefinitionStoreException {
            try {
                //获取对XML文件的验证模式
                int validationMode = getValidationModeForResource(resource);
                //加载XML文件,并得到对应的Document
                Document doc = this.documentLoader.loadDocument(
                        inputSource, getEntityResolver(), this.errorHandler, validationMode, isNamespaceAware());
                //根绝返回的Document注册Bean信息
                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);
            }
        }
    

    在这个方法里面主要做了三件事;
    (1)获取对XML文件的验证模式
    (2)加载XML文件,并的到对应的Document
    (3)根据返回的Document注册Bean信息

    相关文章

      网友评论

          本文标题:1.2.Spring源码解析——容器的基础XmlBeanFact

          本文链接:https://www.haomeiwen.com/subject/pxznmftx.html