Spring IoC 容器初始化(2)

liankou
发布于 2022-4-24 08:43
浏览
0收藏

作者 |jaxer
来源 | WriteOnRead(ID:WriteOnRead)

前情回顾

前文「Spring IoC 容器初始化」 以 IoC 容器中的 ClassPathXmlApplicationContext 为例进行了深入分析。

Spring 从我们的配置文件(即 application-ioc.xml)中读取到 Bean 的原始信息,将其解析为 Document 对象。由于 DOM 解析只是充当了工具(语法解析),不必舍本逐末,这里不再深入分析。

本文继续分析 Spring 如何从 Document 进行语义解析和注册 BeanDefinition。

BeanDefinition 解析和注册

前文提到,Spring 从 Document 解析和注册 BeanDefinition 是在 XmlBeanDefinitionReader#registerBeanDefinitions 方法中实现的,继续跟进这个方法:

public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {
    // ...
    public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
      // 创建 BeanDefinitionDocumentReader 对象
      BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
      int countBefore = getRegistry().getBeanDefinitionCount();
      // 将 Document 中的 Bean 信息注册到容器
      documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
      return getRegistry().getBeanDefinitionCount() - countBefore;
    }

    // ...
}

该方法主要做了两件事:

  1. 创建 BeanDefinitionDocumentReader 对象
  2. 注册 Document 中的 Bean 信息

这里又有个 BeanDefinitionDocumentReader,联系前文的 BeanDefinitionReader、ResourceLoader 等,这就是面向对象编程(OOP)思想的一种典型实现,值得细品。

第二步实际调用了 DefaultBeanDefinitionDocumentReader#doRegisterBeanDefinitions 方法:

public class DefaultBeanDefinitionDocumentReader implements BeanDefinitionDocumentReader {
    // ...

    protected void doRegisterBeanDefinitions(Element root) {
      BeanDefinitionParserDelegate parent = this.delegate;
      // 创建解析 BeanDefinition 的代理对象 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);
          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;
          }
        }
      }
      // 解析 XML 配置文件前做的事情
      preProcessXml(root);
      // 解析 XML 配置文件
      parseBeanDefinitions(root, this.delegate);
      // 解析 XML 配置文件后做的事情
      postProcessXml(root);

      this.delegate = parent;
   }

   // ...
}

该方法创建了一个 BeanDefinitionParserDelegate 对象,看它的名字可知,它是用来解析 BeanDefinition 的代理对象。

在 createDelegate 方法中,还对创建的 BeanDefinitionParserDelegate 进行了初始化,主要是保存了 <beans> 标签的一些默认配置,比如常见的 default-lazy-init、default-autowire、default-init-method 等。

真正对 XML 配置文件进行语义解析的是 parseBeanDefinitions 方法。而且在该方法的前后,Spring 还留出了两个方法 preProcessXml 和 postProcessXml。这两个方法都是空的,用于自定义扩展。

一个框架或软件之所以做得好,除了本身确实好用,「扩展性」也是很重要的一方面。

想起了 Tomcat 有很多参数可配置,JVM 的参数也有一大堆……

下面研究 parseBeanDefinitions 方法是如何解析 Bean 定义的。

public class DefaultBeanDefinitionDocumentReader implements BeanDefinitionDocumentReader {
    // ...

    protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
      // 解析 Spring 默认名称空间
      if (delegate.isDefaultNamespace(root)) {
        NodeList nl = root.getChildNodes();
        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);
      }
   }

   // ...
}

Spring 默认的名称空间是什么呢?

还记得前面的配置文件 application-ioc.xml 吗?如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!-- 测试循环依赖 -->
    <bean id="person" class="com.jaxer.doc.ioc.Person">
        <property name="pet" ref="dog"/>
    </bean>
    <bean id="dog" class="com.jaxer.doc.ioc.Dog">
        <property name="owner" ref="person"/>
        <property name="age" value="1"/>
    </bean>
</beans>

<beans> 标签中的第一行 xmlns 就是它的名称空间,也就是:也就是:http://www.springframework.org/schema/beans

这里暂且跳过 Spring 如何解析自定义标签的 parseCustomElement 方法。先来分析 Spring 如何解析默认名称空间,即 parseDefaultElement 方法:

public class DefaultBeanDefinitionDocumentReader implements BeanDefinitionDocumentReader {
    // ...

    // 解析 Spring 默认名称空间的配置
    private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
      // 解析 import 标签
      if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
        importBeanDefinitionResource(ele);
      }
      // 解析 alias 标签
      else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
        processAliasRegistration(ele);
      }
      // 解析 bean 标签
      else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
        processBeanDefinition(ele, delegate);
      }
      // 解析 beans 标签
      else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
        // recurse
        doRegisterBeanDefinitions(ele);
      }
   }

   // ...
}

这里又分为四个部分,分别解析 import、alias、bean 和 beans 标签。

其实 标签是可以嵌套的,只是很少用到。若嵌套使用了 标签,会继续递归去解析。

下面以 <bean> 标签为例,继续跟进:

public class DefaultBeanDefinitionDocumentReader implements BeanDefinitionDocumentReader {

    protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
      // 通过 BeanDefinitionParserDelegate 解析出定义的 Bean 信息,并封装为 BeanDefinitionHolder 对象
      BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
      if (bdHolder != null) {
        bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
        try {
          // 将 BeanDefinition 注册到注册中心
          BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
        }
        // catch
        getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
      }
    }

}

processBeanDefinition 方法主要做了两件事:

  1. 对 Document 对象中的 Bean 定义进行语义解析,并封装为 BeanDefinitionHolder。BeanDefinitionHolder 持有了 BeanDefinition,并且保存了后者的别名等信息。
  2. 将 BeanDefinition 注册到注册中心,这里的注册中心其实就是 IoC 容器,也就是 DefaultListableBeanFactory。

代码走到这里,还没看到 Spring 如何解析我们定义的 <bean> 标签,以及 <bean> 标签内部的 <property>、<constructor-arg> 等标签……藏得够深啊!别急,马上就到了!

public class BeanDefinitionParserDelegate {
  @Nullable
  public BeanDefinitionHolder parseBeanDefinitionElement(Element ele) {
    return parseBeanDefinitionElement(ele, null);
  }

  @Nullable
  public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
    // 读取 id、name 属性
    String id = ele.getAttribute(ID_ATTRIBUTE);
    String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);

    // 读取别名 alias
    List<String> aliases = new ArrayList<>();
    if (StringUtils.hasLength(nameAttr)) {
      String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
      aliases.addAll(Arrays.asList(nameArr));
    }

    String beanName = id;
    if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
      beanName = aliases.remove(0);
    }

    if (containingBean == null) {
      checkNameUniqueness(beanName, aliases, ele);
    }

    // 解析 <bean> 标签定义的各个属性
    AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
    if (beanDefinition != null) {
      if (!StringUtils.hasText(beanName)) {
        try {
          if (containingBean != null) {
            beanName = BeanDefinitionReaderUtils.generateBeanName(
        beanDefinition, this.readerContext.getRegistry(), true);
          }
          else {
            beanName = this.readerContext.generateBeanName(beanDefinition);
      
            String beanClassName = beanDefinition.getBeanClassName();
            if (beanClassName != null &&
        beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&
        !this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
              aliases.add(beanClassName);
            }
          }
        }
        catch (Exception ex) {
          error(ex.getMessage(), ele);
          return null;
        }
      }
      // 封装为持有 BeanDefinition 对象的 BeanDefinitionHolder
      String[] aliasesArray = StringUtils.toStringArray(aliases);
      return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
    }

    return null;
  }
}

parseBeanDefinitionElement 方法主要做了三件事:

  • 从 Document 读取 <bean> 标签中的 id 和 name 属性
  • 将 Document 中的 Bean 定义转换为 BeanDefinition(实现类为 GenericBeanDefinition)
  • 将 BeanDefinition 封装为 BeanDefinitionHolder 并返回

拿到 BeanDefinitionHolder 后,Spring 会将其注册到注册中心。

到这里我们还是没看到 Spring 是如何解析 <bean> 标签内部的标签的,其实它们是在 parseBeanDefinitionElement 方法中实现的。

留到后面再分析吧,本文先到这里,实在是太枯燥了🤣

小结

本文沿着上篇文章继续跟进,好像没什么实质的东西……算了,就当承前启后吧。

收藏
回复
举报
回复
    相关推荐