Процесс инициализации контейнера Spring IoC (форма xml)

Привыкайте писать вместе! Это 9-й день моего участия в «Новом ежедневном плане Nuggets · Апрельское задание по обновлению», нажмите, чтобы просмотреть подробности мероприятия .

Процесс инициализации контейнера Spring IoC (форма xml)

  • Как работает ИОК?

  • Позиционирование ресурса

  • Загрузить определение компонента

    • Загружать ресурсы с помощью AbstractXmlApplicationContext
  • Зарегистрируйте BeanDefiniton в контейнере -

Общий процесс парсинга xml:

  1. Получить тип ресурса
  2. Местоположение ресурса, найти местоположение ресурса
  3. Используйте соответствующий загрузчик ресурсов, чтобы прочитать файл ресурсов и загрузить соответствующую конфигурацию.
  4. Окончательное действие по загрузке выполняется через уровни прокси и делегирования.

Роль позиционирования ресурсов?

Ресурс — это интерфейс, используемый для инкапсуляции операций ввода-вывода в Spring. При создании контейнера Spring он будет загружать различные типы данных в соответствии с типом конфигурации xml. Наиболее распространенными типами данных являются следующие:

  • FileSystemResource: доступ к ресурсам с абсолютным путем к файлу.

  • ClassPathResourcee: доступ к ресурсам с помощью пути к классам.

  • ServletContextResource: доступ к ресурсам посредством корневого каталога веб-приложения.

  • UrlResource: класс реализации для доступа к сетевым ресурсам. 

  • ByteArrayResource: класс реализации для доступа к ресурсам массива байтов.

Позиционирование ресурса

Зная тип ресурсов, как Resource загружает эти ресурсы? Spring предоставляет интерфейс ResourceLoader для реализации различных стратегий загрузки ресурсов.Все классы реализации 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。

изображение.png

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

изображение.png

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

изображение.png

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

изображение.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);
    }
  }
复制代码

Здесь в качестве конца выбран анализ метки.Вы можете видеть, что здесь передаются два параметра, узел элемента и объект делегата.Сначала действие регистрации контейнера BeanDefineition завершается через объект прокси делегата, а затем Окончательный bean-компонент регистрируется с помощью класса инструмента BeanDefinitionReaderUtils, и bean-компонент окончательно завершает операцию загрузки.

напиши в конце

Конечно, этот процесс является лишь кратким обзором этого метода загрузки, от которого давно отказались. Теперь я более привык использовать метод загрузки классов Java, Форма похожа, но детали разные.

рекомендация

отjuejin.im/post/7085335544414076959
рекомендация