Spring IoC容器初始化过程(xml形式)

简介: Resource是Spring中用于封装I/O操作的接口,在创建Spring的容器的时候,会根据xml对应配置的类型,加载不同的数据类型

Spring IoC容器初始化过程(xml形式)


  • IoC是如何工作的?


  • Resource定位


  • 载入BeanDefinition


  • 使用AbstractXmlApplicationContext加载resource


  • 将BeanDefiniton注册到容器-


xml解析的大致流程:


  1. 获取资源的类型
  2. 资源定位,找到资源所在的位置
  3. 利用相关的资源加载器读取资源文件载入相关的配置。
  4. 通过层层代理和委托完成最后的加载动作。


resource 定位的作用?


Resource是Spring中用于封装I/O操作的接口,在创建Spring的容器的时候,会根据xml对应配置的类型,加载不同的数据类型, 比较常见的数据类型为下面几种:


  • FileSystemResource:以文件绝对路径进行资源访问。


  • ClassPathResourcee:以类路径的方式访问资源。


  • ServletContextResource:web应用根目录的方式访问资源。


  • UrlResource:访问网络资源的实现类。 


  • ByteArrayResource: 访问字节数组资源的实现类。


Resource定位


知道了资源的类型之后,Resource是如何加载这些资源的呢?Spring提供了ResourceLoader接口用于实现不同的Resource加载策略,ApplicationContext的所有实现类都实现RecourceLoader接口,因此可以直接调用getResource(参数)获取Resoure对象。ApplicationContext也是存在子类的,根据资源的类型有不同的资源上下文,分别对应不同资源类型的加载。


但是有了资源和资源的加载器之后,资源如何注入到spring当中,spring又是如何管理的呢?这里就涉及到ioc的一个重要对象,BeanDefinition


载入BeanDefinition


BeanDefinition是根据resource对象中的bean来生成的,所以最终Bean会以BeanDefinition的形式存在,spring的配置主要为xml的格式,所以会使用AbstractXmlApplicationContext进行加载,其中有一个

loadBeanDefinitions(DefaultListableBeanFactory beanFactory) 方法获取。


在代码内,存在一个叫做BeanDefinitionReader的对象用于解析bean的xml属性,接着BeanDefinitionReader会设置相关的环境和注入当前的上下文对象,注入一个属性解析用于解析xml属性内容。最后通过当前的上下文载入beandefine。


image.png


我们顺着this.loadBeanDefinition(), 在内部通过xmlBeanDefinitionReader进行操作,分别加载resourse和本地的配置位置。加载的细节在xmlBeanDefinitionReader当中,我们接着顺着xmlBeanDefinitionReader.loadBeanDefinitions看下他是如何加载资源的。


image.png


可以看到,加载资源是重载了一个AbstractBeanDefinitionReader的loadBeanDefinitions方法,其实可以看作是讲加载资源的操作最终委托给了xmlBeanDefinitionReader


image.png


下面是相关的内容和方法:


image.png


从上面的代码可以看到实际调用的就是子类加载资源的方法,由于这里是XML的方式加载我们可以接着看一下:


public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
    Assert.notNull(encodedResource, "EncodedResource must not be null");
    if (logger.isTraceEnabled()) {
      logger.trace("Loading XML bean definitions from " + encodedResource);
    }
    // 从threadlocal上下文中获取
    Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
    // 讲资源绑定到当前线程
    if (!currentResources.add(encodedResource)) {
      throw new BeanDefinitionStoreException(
          "Detected cyclic loading of " + encodedResource + " - check your import definitions!");
    }
    // 设置编码,同事加载IO流
    try (InputStream inputStream = encodedResource.getResource().getInputStream()) {
      InputSource inputSource = new InputSource(inputStream);
      if (encodedResource.getEncoding() != null) {
        inputSource.setEncoding(encodedResource.getEncoding());
      }
      return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
    }
    catch (IOException ex) {
      throw new BeanDefinitionStoreException(
          "IOException parsing XML document from " + encodedResource.getResource(), ex);
    }
    finally {
      // 加载完成之后需要进行卸载操作
      currentResources.remove(encodedResource);
      if (currentResources.isEmpty()) {
        this.resourcesCurrentlyBeingLoaded.remove();
      }
    }
  }
  // 加载资源
  protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
      throws BeanDefinitionStoreException {
    try {
      Document doc = doLoadDocument(inputSource, resource);
      int count = registerBeanDefinitions(doc, resource);
      if (logger.isDebugEnabled()) {
        logger.debug("Loaded " + count + " bean definitions from " + resource);
      }
      return count;
    }
    // 根不同异常进行处理
  }

接着是xml加载资源的具体细节,首先在加载资源之前,需要对于资源进行一次校验的操作,然后从资源当中加载document对象,大致过程为:从resource中吧资源文件读到document中,那么如何读取到document中?这里借助了documentLoader对象进行加载,XMLBeanDefine会创建一个DefaultDocumentLoader的私有属性,使用loadDocument方法加载,下面根据描述跟进一下具体代码:


/**
 getValidationModeForResource(resource) 校验过后的资源
 isNamespaceAware() 命名空间感知
 getEntityResolver()
*/
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
    return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
        getValidationModeForResource(resource), isNamespaceAware());
  }

内部的细节不做过多的拓展,接着来看一下registerBeanDefinitions(doc, resource),这个方法的主要内容是对于Spring的内容进行语义的转化,变为beanDefinision的类型,这里同样通过构建一个BeanDefinitionDocumentReader对象完成创建的工作。


public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
    // 获取BeanDefinitionDocumentReader实例
    BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
    // 获取容器当中bean的数量
    int countBefore = getRegistry().getBeanDefinitionCount();
    // 注入bean并且设置资源上下文
    documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
    return getRegistry().getBeanDefinitionCount() - countBefore;
  }


接着我们可以看一下注入bean的具体操作,这里关注registerBeanDefinition方法,


protected void doRegisterBeanDefinitions(Element root) {
    /*任何嵌套的 <beans> 元素都将导致此方法中的递归。 在
    为了正确传播和保留 <beans> default-* 属性,
     跟踪当前(父)委托,它可能为空。 创建
     新的(子)委托,带有对父级的引用,用于回退目的,
    然后最终将 this.delegate 重置回其原始(父)引用。
    这种行为模拟了一堆委托,而实际上并不需要一个。
    */
    BeanDefinitionParserDelegate parent = this.delegate;
    // 创建委托对象BeanDefinitionParserDelegate,将dom解析委托给BeanDefinitionParserDelegate完成
    this.delegate = createDelegate(getReaderContext(), root, parent);
    if (this.delegate.isDefaultNamespace(root)) {
      String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
      if (StringUtils.hasText(profileSpec)) {
        String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
            profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
        // 我们不能使用 Profiles.of(...) 因为 XML 配置中不支持配置文件表达式。 有关详细信息,请参阅 SPR-12458。
        if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
          if (logger.isDebugEnabled()) {
            logger.debug("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;
  }

这里可能会有疑问,都已经进行dom解析了为什么还需要一个委托对象来完成?BeanDefinitionParserDelegate 对象这里涉及到JDK和CGLIB动态代理的知识,简单来说这个代理类会完成符合SPring bean语义规则的处理,比如BeanDefinitionParseDelegate代理中的parseBeanDefinition会对于xml文件中的节点解析,通过遍历import标签节点调用importBeanDefinitionResource方法处理,接着利用processBeanDefinition方法进行处理。


记者我们看一下核心方法的内容,这个比较好理解,就是一个典型的dom解析工作:


protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
    if (delegate.isDefaultNamespace(root)) {
      NodeList nl = root.getChildNodes();
      // 遍历节点,进行解析的操作,遍历到《import》标签节点调用importBeanDefinitionResource(ele)进行处理
      // 遍历到bean 使用
      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);
    }
  }
  // 对于dom内容进行解析,
 private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
    if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
      importBeanDefinitionResource(ele);
    }
    else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
      processAliasRegistration(ele);
    }
    else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
      processBeanDefinition(ele, delegate);
    }
    else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
      // recurse
      doRegisterBeanDefinitions(ele);
    }
  }

这里选择标签的解析作为结尾,可以看到这里传递了两个参数,Element节点和一个delegate对象,首先通过delegate代理对象完成BeanDefineition的容器注册动作,然后通过工具类BeanDefinitionReaderUtils 注册最终的bean,最终完成bean的加载操作。


写在最后


当然这个过程只是简单回顾一下这种加载方式在现在其实也早就被抛弃了。现在更为习惯使用Java类的加载方式,其实形式大同小异,只是细节有差异而已。

相关文章
|
21天前
|
XML Java 数据格式
Spring(一)IOC小案例
Spring(一)IOC小案例
|
1月前
|
Java 容器 Spring
【spring(一)】核心容器总结
【spring(一)】核心容器总结
|
1月前
|
Java 开发者 容器
【Java】深入了解Spring容器的两个关键组件
【Java】深入了解Spring容器的两个关键组件
10 0
|
1月前
|
XML Java 数据格式
Spring 的奇幻起源:从 IoC 容器到 Bean 的魔法世界 (下)
Spring 的奇幻起源:从 IoC 容器到 Bean 的魔法世界
|
1月前
|
XML Java 数据格式
Spring 的奇幻起源:从 IoC 容器到 Bean 的魔法世界 (上)
Spring 的奇幻起源:从 IoC 容器到 Bean 的魔法世界 (上)
|
1月前
|
Java Go 开发者
Docker容器技术简介及其与Go语言的结合点
【2月更文挑战第23天】本文首先概述了Docker容器技术的核心概念和优势,接着探讨了Go语言与Docker容器技术的结合点。通过阐述Docker的轻量级、可移植性和版本控制等特性,以及Go语言在容器化应用中的优势,本文旨在说明两者结合能够实现更高效、灵活的应用开发和部署。
|
1月前
|
Oracle 关系型数据库 数据库
|
6天前
|
存储 运维 监控
构建高效稳定的Docker容器监控体系
【4月更文挑战第18天】 在现代微服务架构中,Docker容器已成为部署和运行应用的标准环境。随之而来的挑战是如何有效监控这些容器的性能与健康状况,确保系统的稳定性和可靠性。本文将探讨构建一个高效稳定的Docker容器监控体系的关键技术和方法,包括日志管理、性能指标收集以及异常检测机制,旨在为运维人员提供实用的指导和建议。
11 0
|
15天前
|
Linux Docker 容器
docker 容器常用命令
docker 容器常用命令
13 0
|
15天前
|
Linux Shell 虚拟化
linux 部署docker容器虚拟化平台(二)--------docker 镜像制作方法
linux 部署docker容器虚拟化平台(二)--------docker 镜像制作方法
26 0