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

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: Spring IoC容器初始化过程(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类的加载方式,其实形式大同小异,只是细节有差异而已。

相关文章
|
3月前
|
XML 存储 JSON
Twaver-HTML5基础学习(19)数据容器(2)_数据序列化_XML、Json
本文介绍了Twaver HTML5中的数据序列化,包括XML和JSON格式的序列化与反序列化方法。文章通过示例代码展示了如何将DataBox中的数据序列化为XML和JSON字符串,以及如何从这些字符串中反序列化数据,重建DataBox中的对象。此外,还提到了用户自定义属性的序列化注册方法。
50 1
|
2天前
|
XML Java 数据格式
Spring容器Bean之XML配置方式
通过对以上内容的掌握,开发人员可以灵活地使用Spring的XML配置方式来管理应用程序的Bean,提高代码的模块化和可维护性。
17 6
|
4月前
|
XML Java 数据格式
Spring5入门到实战------7、IOC容器-Bean管理XML方式(外部属性文件)
这篇文章是Spring5框架的实战教程,主要介绍了如何在Spring的IOC容器中通过XML配置方式使用外部属性文件来管理Bean,特别是数据库连接池的配置。文章详细讲解了创建属性文件、引入属性文件到Spring配置、以及如何使用属性占位符来引用属性文件中的值。
Spring5入门到实战------7、IOC容器-Bean管理XML方式(外部属性文件)
|
4月前
|
缓存 Java 数据库连接
Spring Boot奇迹时刻:@PostConstruct注解如何成为应用初始化的关键先生?
【8月更文挑战第29天】作为一名Java开发工程师,我一直对Spring Boot的便捷性和灵活性着迷。本文将深入探讨@PostConstruct注解在Spring Boot中的应用场景,展示其在资源加载、数据初始化及第三方库初始化等方面的作用。
96 0
|
1月前
|
缓存 Java Spring
实战指南:四种调整 Spring Bean 初始化顺序的方案
本文探讨了如何调整 Spring Boot 中 Bean 的初始化顺序,以满足业务需求。文章通过四种方案进行了详细分析: 1. **方案一 (@Order)**:通过 `@Order` 注解设置 Bean 的初始化顺序,但发现 `@PostConstruct` 会影响顺序。 2. **方案二 (SmartInitializingSingleton)**:在所有单例 Bean 初始化后执行额外的初始化工作,但无法精确控制特定 Bean 的顺序。 3. **方案三 (@DependsOn)**:通过 `@DependsOn` 注解指定 Bean 之间的依赖关系,成功实现顺序控制,但耦合性较高。
实战指南:四种调整 Spring Bean 初始化顺序的方案
|
1月前
|
Kubernetes 容器 Perl
【赵渝强老师】K8s中Pod中的初始化容器
Kubernetes的Pod包含业务容器、基础容器、初始化容器和临时容器。初始化容器在业务容器前运行,用于执行必要的初始化任务。本文介绍了初始化容器的作用、配置方法及优势,并提供了一个示例。
|
2月前
|
XML Java 数据格式
Spring IOC容器的深度解析及实战应用
【10月更文挑战第14天】在软件工程中,随着系统规模的扩大,对象间的依赖关系变得越来越复杂,这导致了系统的高耦合度,增加了开发和维护的难度。为解决这一问题,Michael Mattson在1996年提出了IOC(Inversion of Control,控制反转)理论,旨在降低对象间的耦合度,提高系统的灵活性和可维护性。Spring框架正是基于这一理论,通过IOC容器实现了对象间的依赖注入和生命周期管理。
79 0
|
3月前
|
XML Java 开发者
经典面试---spring IOC容器的核心实现原理
作为一名拥有十年研发经验的工程师,对Spring框架尤其是其IOC(Inversion of Control,控制反转)容器的核心实现原理有着深入的理解。
151 3
|
2月前
|
SQL Shell 数据库
在TDengine容器中创建初始化数据库的Shell命令实例
以上就是在Docker容器环境中部署并初始化TDengine数据库的全过程,希望对你有所帮助。
88 0
|
4月前
|
XML Java 数据格式
Spring5入门到实战------8、IOC容器-Bean管理注解方式
这篇文章详细介绍了Spring5框架中使用注解进行Bean管理的方法,包括创建Bean的注解、自动装配和属性注入的注解,以及如何用配置类替代XML配置文件实现完全注解开发。
Spring5入门到实战------8、IOC容器-Bean管理注解方式
下一篇
DataWorks