一文带你深入理解Spring中的xml解析过程与BeanDefinition的注册

475 阅读14分钟

前言

本篇将会带你认识以下几个问题

  • 什么是Resource?
  • BeanDefinition又是什么?
  • xml的解析配置过程是怎样的?
  • 什么是默认标签和自定义标签?
  • 默认标签的解析过程又是怎样的?
  • 什么是BeanDefinition的注册?

带着以上这几个问题,我们开启本篇的叙述。

总体流程

在分析前,我们先看下Spring在注册BeanDefinition的过程中,核心对象的变化过程。

过程简单描述如下:

  • 找到location指定的xml文件
  • 将xml文件转换为Resource
  • 将Resource包装成EncodedResource
  • 获取到Document对象
  • 根据上面得到的Document和Resource对象,抽象成BeanDefinition,并完成注册
  • 实例化bean

相关概念的说明

什么是Resource

Resource即为资源的定义规范。在Spring中,将资源的定义和加载分开处理了。资源的加载是通过ResourceLoader这个接口定义的规范进行处理的。这里不对Resource和ResourceLoader进行扩展说明,只做简单的说明即可,有机会单独写一篇文章阐述。 我们来看下Resource的源码

public interface Resource extends InputStreamSource {
	boolean exists();
	default boolean isReadable() {
		return exists();
	}
	default boolean isOpen() {
		return false;
	}
	default boolean isFile() {
		return false;
	}
	URL getURL() throws IOException;
	URI getURI() throws IOException;
	File getFile() throws IOException;
	default ReadableByteChannel readableChannel() throws IOException {
		return Channels.newChannel(getInputStream());
	}
	long contentLength() throws IOException;
	long lastModified() throws IOException;
	Resource createRelative(String relativePath) throws IOException;
	String getFilename();
	String getDescription();
}

从源码里,能够知道,Resource是对我们定义的xml资源文件属性的统一描述,比如文件是否存在、是否可读、是否为文件、文件长度、URI、文件名等。等到下面的BeanDefinition时,可以知道BeanDefinition其实是对xml中元素标签等具体内容的描述。

什么是BeanDefinition?

在讲述它的含义前,我们来看一下这个类(接口)的定义,由于篇幅太长,这里截取一部分。

package org.springframework.beans.factory.config;

public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {
	
	void setParentName(@Nullable String parentName);
	String getParentName();
	void setBeanClassName(@Nullable String beanClassName);
	String getBeanClassName();
	void setScope(@Nullable String scope);
	String getScope();
	void setLazyInit(boolean lazyInit);
	boolean isLazyInit();
	void setDependsOn(@Nullable String... dependsOn);
	String[] getDependsOn();
	void setAutowireCandidate(boolean autowireCandidate);
	boolean isAutowireCandidate();
	void setPrimary(boolean primary);
	boolean isPrimary();
	void setInitMethodName(@Nullable String initMethodName);
	String getInitMethodName();
	boolean isSingleton();
	boolean isPrototype();
	boolean isAbstract();
	// ....
}

看到上面的scope、lazyInit、InitMethodName、isSingleton、ParentName、isAbstract... 这些方法,是不是很熟悉?没错,其实是和spring的xml文件对应的。根据这点,可以得出一个结论:Spring经过一系列复杂的处理之后,将xml元素转换成了BeanDefinition对象。那这个过程又是如何的?为什么要抽象出这个类?

<bean id="xxxxx" name="xxxxx" abstract="true" depends-on="userDaoImpl" init-method="xx" lazy-init="true" scope="singleton" primary="true"></bean>

BeanDefinition的注册与Bean的实例化

  • 一般而言,所谓的Bean定义信息的注册,或者说是BeanDefinition的注册,其实就是将由xml经过一系列的处理得到的BeanDefinition,存入Spring的BeanFactory中,这个过程就是所谓的注册。具体保存在了DefaultListableBeanFactory类的beanDefinitionMap和beanDefinitionNames这两个属性之中。
  • Bean的实例化,是在BeanDefinition注册完成之后进行的,通过遍历所有注册的BeanDefinition,完成Bean的实例化和属性填充等,成为一个可用的Bean实例。

小结

通过上面的分析,知道了Spring如何将资源的定义和加载进行的区分。而对实例信息的注册和实例对象的创建,同样也进行了解耦。

obtainFreshBeanFactory方法中默认标签的解析和Bean定义的注册

资源的加载和BeanDefinition的注册是在AbstractApplicationContext类的refresh方法中通过调用obtainFreshBeanFactory()方法完成整个过程的。我们来看obtainFreshBeanFactory()方法的源码:

  # AbstractApplicationContext
  protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
      refreshBeanFactory();
      return getBeanFactory();
  }

其中资源的加载和定义信息的注册主要是在refreshBeanFactory()中完成。该方法的源码如下:

  # AbstractRefreshableApplicationContext
  protected final void refreshBeanFactory() throws BeansException {
        // 如果beanFactory存在了, 就先销毁
        if (hasBeanFactory()) {
            destroyBeans();
            closeBeanFactory();
        }
        try {
            // 创建一个DefaultListableBeanFactory对象实例, 并在以下代码中设置序列号等属性
            DefaultListableBeanFactory beanFactory = createBeanFactory();
            beanFactory.setSerializationId(getId());
            
            customizeBeanFactory(beanFactory);
            
            // 加载所有的BeanDefinitions,实际解析xml的位置
            loadBeanDefinitions(beanFactory);
            
            this.beanFactory = beanFactory;
        }
        catch (IOException ex) {
            throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
        }
    }

该方法比较简单,主要完成两件事:

  • 确保创建一个新的beanFactory、并设置相关的属性信息
  • 加载BeanDefinition信息并完成注册 我们再来简单看一下里面的 customizeBeanFactory(beanFactory) 这个方法:
  protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) {
        // 是否允许 BeanDefinition的覆盖, 默认true
        if (this.allowBeanDefinitionOverriding != null) {
            beanFactory.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
        }

        // 是否允许 循环依赖, 默认true
        if (this.allowCircularReferences != null) {
            beanFactory.setAllowCircularReferences(this.allowCircularReferences);
        }
    }

该方法中设置了两个属性allowBeanDefinitionOverriding和allowCircularReferences,分别代表是否允许BeanDefinition的覆盖和是否允许循环依赖注入。

  • BeanDefinition覆盖:简单说明一下,该参数的作用就是告诉spring,在注册BeanDefinition时,若存在相同的BeanDefinition,是否允许覆盖。部分逻辑代码如下。若不允许覆盖,则抛出异常;若允许覆盖,则记录一些日志,并用新的BeanDefinition进行覆盖。
  • 循环依赖:这个处理比较复杂,这里不做过多的说明, 那么这里就引申出一个问题,我们在使用框架时,如何修改这两个属性?只需要在继承ClassPathXmlApplicationContext的类中,重写customizeBeanFactory()方法,在该方法中直接设置这两个值为false或true即可。
  # DefaultListableBeanFactory
  @Override
  public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionStoreException {
      BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
      if (existingDefinition != null) {
          if (!isAllowBeanDefinitionOverriding()) {
              throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);
          }
          else if (existingDefinition.getRole() < beanDefinition.getRole()) {
              // 日志记录
          }
          else if (!beanDefinition.equals(existingDefinition)) {
              // 日志记录
          }
          else {
              // 日志记录
          }
          this.beanDefinitionMap.put(beanName, beanDefinition);
      }
  }

下面再来看loadBeanDefinitions(beanFactory)方法

  # AbstractXmlApplicationContext
  protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
      // 通过委托XmlBeanDefinitionReader去解析BeanDefinition
      // 基于注解的方式则是使用AnnotationBeanDefinitionReader去解析BeanDefinition

      // 还要注意一点的是, 该读取器的构造函数的参数是BeanDefinitionRegistry类型, 此处beanFactory对应的实际类型为DefaultListableBeanFactory
      // 而DefaultListableBeanFactory实现了BeanDefinitionRegistry接口
      XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

      beanDefinitionReader.setEnvironment(this.getEnvironment());
      beanDefinitionReader.setResourceLoader(this);

      // 留意ResourceEntityResolver类, 处理和xml命名空间相关的schemal相关的文件
      // ResourceEntityResolver及其父类中,映射了xml中网络xsd和本地存储的xsd文件的映射位置, 感兴趣的可以跟踪构造函数查看
      beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

      // 设置两项属性值:validationMode = VALIDATION_AUTO, 即等于1; namespaceAware = false
      initBeanDefinitionReader(beanDefinitionReader);

      // 使用委托方式进行BeanDefinition的解析
      loadBeanDefinitions(beanDefinitionReader);
  }

继续跟踪最后一行的 loadBeanDefinitions(beanDefinitionReader) 源码:

  # AbstractXmlApplicationContext
  protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
      Resource[] configResources = getConfigResources();
      if (configResources != null) {
          reader.loadBeanDefinitions(configResources);
      }
      String[] configLocations = getConfigLocations();
      if (configLocations != null) {
          reader.loadBeanDefinitions(configLocations);
      }
  }

这里看一下这个getConfigLocations()这个方法,该方法从AbstractRefreshableConfigApplicationContext类的configLocations这个属性变量中获取所有的位置信息,这个变量是一个String类型的数组。那这个属性值是在什么时候赋值的呢?其实是在ClassPathXmlApplicationContext的构造函数中,调用了setConfigLocations(configLocations)设置进去的。

继续往下:

  # AbstractBeanDefinitionReader
  @Override
  public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
      Assert.notNull(locations, "Location array must not be null");
      int count = 0;
      for (String location : locations) {
          count += loadBeanDefinitions(location);
      }
      return count;
  }

这里遍历了传过来的位置,继续看源码, 其中省略了日志和异常等不重要的源码:

  # AbstractBeanDefinitionReader
  public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
      // resourceLoader的类型为ClassPathXmlApplicationContext
      ResourceLoader resourceLoader = getResourceLoader();
      if (resourceLoader == null) {
          throw new BeanDefinitionStoreException(
                  "Cannot load bean definitions from location [" + location + "]: no ResourceLoader available");
      }

      if (resourceLoader instanceof ResourcePatternResolver) {
          try {
              // 将location对应路径下的资源文件抽象成Resource对象
              Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);

              // 核心方法
              int count = loadBeanDefinitions(resources);
              if (actualResources != null) {
                  Collections.addAll(actualResources, resources);
              }

              return count;
          }
          catch (IOException ex) {
              throw new BeanDefinitionStoreException(
                      "Could not resolve bean definition resource pattern [" + location + "]", ex);
          }
      }
      else {
          Resource resource = resourceLoader.getResource(location);
          int count = loadBeanDefinitions(resource);
          if (actualResources != null) {
              actualResources.add(resource);
          }
          return count;
      }
  }

方法看起来较多,但其实主要完成了一件事:由传入的location位置,找到对应的xml,并抽象成Resource对象,再通过loadBeanDefinitions方法完成BeanDefinition的注册。 下面继续跟踪loadBeanDefinitions方法:

  # AbstractBeanDefinitionReader
  @Override
  public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException {
      Assert.notNull(resources, "Resource array must not be null");
      int count = 0;
      for (Resource resource : resources) {
          count += loadBeanDefinitions(resource);
      }
      return count;
  }

此时的参数类型为Resource,省略中间的调用部分,继续跟踪:

  # XmlBeanDefinitionReader
  public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
      Assert.notNull(encodedResource, "EncodedResource must not be null");

	  // 使用ThreadLocal
      Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();

      // 若返回false, 说明集合中存在了, 抛出异常
      if (!currentResources.add(encodedResource)) {
          throw new BeanDefinitionStoreException(
                  "Detected cyclic loading of " + encodedResource + " - check your import definitions!");
      }

      try (InputStream inputStream = encodedResource.getResource().getInputStream()) {
          InputSource inputSource = new InputSource(inputStream);
          if (encodedResource.getEncoding() != null) {
              inputSource.setEncoding(encodedResource.getEncoding());
          }
          // 最终实现将Resource转换成BeanDefinition
          return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
      }
      catch (IOException ex) {
          throw new BeanDefinitionStoreException(
                  "IOException parsing XML document from " + encodedResource.getResource(), ex);
      }
      finally {
          // 处理结束之后, 从集合中移除
          currentResources.remove(encodedResource);
          // 清空ThreadLocal
          if (currentResources.isEmpty()) {
              this.resourcesCurrentlyBeingLoaded.remove();
          }
      }
  }

方法中resourcesCurrentlyBeingLoaded属性的定义为:ThreadLocal<Set>,使用线程本地变量,存储编码过后的Resource资源。通过ThreadLocal和Set来保证,一个线程尚未处理完成Resource,又有另一个线程进入该方法,不会再去处理该Resource资源。

再来看doLoadBeanDefinitions(..)方法,源码如下,省略异常处理代码:

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException {
      try {
          // inputSource包装了从Resource中读取的输入流,
          // 根据xml配置文件, 获得Document对象
          // XML的解析是基于更高效的SAX的方式
          Document doc = doLoadDocument(inputSource, resource);
          int count = registerBeanDefinitions(doc, resource);
          if (logger.isDebugEnabled()) {
              logger.debug("Loaded " + count + " bean definitions from " + resource);
          }
          return count;
      }
      catch (BeanDefinitionStoreException ex) {
          throw ex;
      }
  }

看下doLoadDocument方法:

protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
      // 留意getValidationModeForResource(resource)
      // 实际上该方法的作用为:探测xml的模式,
      // 返回值有四种情况之一:XmlValidationModeDetector.VALIDATION_NONE=0 | VALIDATION_AUTO=1 | VALIDATION_DTD=2 | VALIDATION_XSD=3
      // 根据返回的模式不同, 使用不同的方式验证. 例如, 返回的是2, 则使用基于DTD的方式进行xml格式的验证
      return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
              getValidationModeForResource(resource), isNamespaceAware());
  }

该方法得到一个Document对象并返回,其中的getValidationModeForResource方法是用来分析xml的模式,以便作格式校验。 下面重点分析registerBeanDefinitions方法,源码如下:

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
      // 获取Document读取器, 通过该对象来读取文档内容
      BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
      int countBefore = getRegistry().getBeanDefinitionCount();

      // 由documentReader完成Document的解析, 并完成BeanDefinition的注册工作
      /**
       * createReaderContext:
       * 	这里createReaderContext()创建的XmlReadContext, 在解析自定义标签/命名空间的时候, 会使用到该对象
       *    同时初始化了自定义标签的解析处理器(根据 spring-beans模块里的"META-INF/spring.handlers"文件)
       */
      documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
      return getRegistry().getBeanDefinitionCount() - countBefore;
  }

主要完成两件事:

  • 通过反射创建一个Document读取器,负责DOM的解析工作
  • 注册BeanDefinition,并统计注册前后的BeanDefinition的数量

继续跟踪registerBeanDefinitions()方法:

  # DefaultBeanDefinitionDocumentReader
  public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
      this.readerContext = readerContext;
      doRegisterBeanDefinitions(doc.getDocumentElement());
  }
# DefaultBeanDefinitionDocumentReader
protected void doRegisterBeanDefinitions(Element root) {
    BeanDefinitionParserDelegate parent = this.delegate;
    this.delegate = createDelegate(getReaderContext(), root, parent);

    if (this.delegate.isDefaultNamespace(root)) {
        // 和profile相关的处理逻辑
        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)) {
                return;
            }
        }
    }

    preProcessXml(root);
    // 解析的主要方法
    parseBeanDefinitions方法(root, this.delegate);
    postProcessXml(root);

    this.delegate = parent;
}

主要做了两件事

  • 处理profile相关的逻辑,profile在springboot中使用,可以方便的切换各种环境
  • 解析注册BeanDefinition、并在前后提供两个可以扩展的模板方法

下面重点分析 parseBeanDefinitions 方法

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
      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);
      }
  }

方法分别解析了默认的标签元素和自定义的标签元素。这里解释一下这两个概念

  • 自定义标签:以<xxx:xx>指定前缀形式的声明,如 <context:component-scan/><aop:config>等,这些前缀需要在xml头部的命名空间中引入。
  • 默认标签:最常用的就是<bean>标签了,即不需要指定前缀,类似的还有<import>

本文着重分析默认标签的解析。关于自定义标签,后续文章会详细说明。继续看parseDefaultElement方法

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

通过上面的源码,也可以知道,默认的标签一共有四个,在这里通过四个if分别进行了处理:import、alias、bean、beans。

  • importBeanDefinitionResource:spring的xml文件可以分成多各文件,通过import进行汇总。解析的时候就会用到此方法,实际是个递归方法,这里不做说明。
  • processAliasRegistration:处理alias别名标签,例如下面的xml声明中,person是user的别名,user和person指的都是同一个实例,不重要。
  • processBeanDefinition:解析bean标签,核心处理逻辑,下面会重点来说。
  • doRegisterBeanDefinitions:处理beans,又是一个递归处理,不重要。

别名标签的使用:

    <alias name="user" alias="person"></alias>
    <bean id="user" class="xxx.xxx.User">

来看processBeanDefinition()方法:

# DefaultBeanDefinitionDocumentReader
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
    // 通过代理解析Element节点
    BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
    if (bdHolder != null) {
        // 看名字应该是修饰Bean的定义信息的,没仔细看过。
        bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
        try {
            // 上面解析完成后, 这里开始进行注册BeanDefinition
            BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
        }
        catch (BeanDefinitionStoreException ex) {
            getReaderContext().error("Failed to register bean definition with name '" +
                    bdHolder.getBeanName() + "'", ele, ex);
        }
        // Send registration event.
        getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
    }
}

该方法主要完成三件事:

  • 解析得到一个BeanDefinition的包装类
  • 完成BeanDefinition的注册
  • 触发一个通知事件

先看第一件事,也即第一行的委派解析delegate.parseBeanDefinitionElement(ele), 返回的是一个BeanDefinition实例的包装类,来看源码:

  # BeanDefinitionParserDelegate
  public BeanDefinitionHolder parseBeanDefinitionElement(Element ele) {
      // 调用了下面的解析方法
      return parseBeanDefinitionElement(ele, null);
  }

  // 解析
  public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
      String id = ele.getAttribute(ID_ATTRIBUTE);
      String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
      
      // 处理别名
      List<String> aliases = new ArrayList<>();
      if (StringUtils.hasLength(nameAttr)) {
          String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
          aliases.addAll(Arrays.asList(nameArr));
      }
      
      // 默认id为beanName
      String beanName = id;
      if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
          // 如果没有配置id的话,将会取第一个别名当做beanName
          beanName = aliases.remove(0);
      }
      
      // 会进入if里,因为传过来的就是值就是null
      if (containingBean == null) {
          // 校验beanName、alias是否重复
          // 通过HashSet<String>用来存所有处理过的name, 防止重复处理, 该集合变量定义在BeanDefinitionParserDelegate#usedNames中
          checkNameUniqueness(beanName, aliases, ele);
      }

      // 这里就开始解析xml的剩余元素了,得到一个beanDefinition
      // 上面只是解析了核心的id, bean中其他详细的属性和子标签, 则在此处进行了解析处理
      AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
      if (beanDefinition != null) {
          if (!StringUtils.hasText(beanName)) {
              try {
                  if (containingBean != null) {
                      // 生成bean的变量名称
                      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;
              }
          }
          
          String[] aliasesArray = StringUtils.toStringArray(aliases);
          // 把beanDefinition封装成Holder
          return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
      }
      return null;
  }

上面的源码完成了BeanDefinition包装类BeanDefinitionHolder实例的创建,分两步执行:

  • 优先处理id、name和别名
  • 解析bean标签的其他属性、以及子标签:parseBeanDefinitionElement()方法

在分析步骤二之前,我们先来深入分析BeanDefinition,在文章的开头,简单描述了什么是BeanDefinition:xml文件内容的类形式描述。BeanDefinition实际是一个接口,继承关系如下图:

三个类简单说明:

  • AbstractBeanDefinition:BeanDefinition接口的中规范的抽象实现类、包括BeanDefinition的默认属性的设置,如scop、懒加载等
  • GenericBeanDefinition:继承自AbstractBeanDefinition,新增了一个parent属性。一般使用这个类即可。
  • RootBeanDefinition:可合并的BeanDefinition。在Spring实例化bean的时候,getMergedLocalBeanDefinition(..)方法返回的就是RootBeanDefinition。这个和父子bean结合使用的时候,子的BeanDefinition,是可以覆盖父的BeanDefinition中相同的属性的。 Spring中关于BeanDefinition的类很多,有兴趣可以查阅相关的文章。在本篇的文末会给出父子Bean的使用参考。

下面可以继续说明步骤二了:parseBeanDefinitionElement(..),省略了异常和finally,代码如下:

  # BeanDefinitionParserDelegate
  public AbstractBeanDefinition parseBeanDefinitionElement(
          Element ele, String beanName, @Nullable BeanDefinition containingBean) {

      this.parseState.push(new BeanEntry(beanName));

      // 获取class属性
      String className = null;
      if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
          className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
      }

      // 获取parent属性,文末会给出用法参考
      String parent = null;
      if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
          parent = ele.getAttribute(PARENT_ATTRIBUTE);
      }

      try {
          // 直接通过new的方式, 创建了一个GenericBeanDefinition的实例,
          // 并设置parentName属性值, parentName属性是
          AbstractBeanDefinition bd = createBeanDefinition(className, parent);
          // 解析xml中bean标签中的属性:
          // 包括:scope、autowrite、abstract lazy-init  depends-on init-method  destroy-method factory-method factory-bean
          parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
          // 
          bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));


          // 下面开始解析bean标签中的字标签
          // 解析meta子标签:<meta key="key1" value="固定值"/>
          parseMetaElements(ele, bd);

          // 解析lockup-method子标签:<lookup-method name="方法名" bean="bean名称"/>
          parseLookupOverrideSubElements(ele, bd.getMethodOverrides());

          // 解析replaced-method子标签:
          //     <replaced-method name="方法名" replacer="bean名称">
          // 	       <arg-type>参数类型,用于确认唯一的方法</arg-type>
          //     </replaced-method>
          parseReplacedMethodSubElements(ele, bd.getMethodOverrides());

          // 解析constructor-arg子标签:<constructor-arg ref="bean" value="固定值" type="参数类型" name="参数名称" index="索引"/>
          parseConstructorArgElements(ele, bd);

          // 解析property子标签:<property name="key1" value="固定值" ref="beanRef"/>
          parsePropertyElements(ele, bd);

          // 解析qualifier子标签:<qualifier type="bean类型" value="限定的bean的名称"/>
          parseQualifierElements(ele, bd);

          bd.setResource(this.readerContext.getResource());
          bd.setSource(extractSource(ele));
          this.parseState.pop();
          return bd;
      }
      // 省略异常处理

      return null;
  }

该方法主要分为两大块:

  • 解析bean标签的各种属性值,设置到创建好的BeanDefinition实例中
  • 解析bean标签的子标签

先看解析bean的属性值源码:

  public AbstractBeanDefinition parseBeanDefinitionAttributes(Element ele, String beanName,
          BeanDefinition containingBean, AbstractBeanDefinition bd) {
      if (ele.hasAttribute(SINGLETON_ATTRIBUTE)) {
          error("Old 1.x 'singleton' attribute in use - upgrade to 'scope' declaration", ele);
      } else if (ele.hasAttribute(SCOPE_ATTRIBUTE)) {
          bd.setScope(ele.getAttribute(SCOPE_ATTRIBUTE));
      } else if (containingBean != null) {
          bd.setScope(containingBean.getScope());
      }

      if (ele.hasAttribute(ABSTRACT_ATTRIBUTE)) {
          bd.setAbstract(TRUE_VALUE.equals(ele.getAttribute(ABSTRACT_ATTRIBUTE)));
      }

      String lazyInit = ele.getAttribute(LAZY_INIT_ATTRIBUTE);
      if (isDefaultValue(lazyInit)) {
          lazyInit = this.defaults.getLazyInit();
      }
      bd.setLazyInit(TRUE_VALUE.equals(lazyInit));

      String autowire = ele.getAttribute(AUTOWIRE_ATTRIBUTE);
      bd.setAutowireMode(getAutowireMode(autowire));

      if (ele.hasAttribute(DEPENDS_ON_ATTRIBUTE)) {
          String dependsOn = ele.getAttribute(DEPENDS_ON_ATTRIBUTE);
          bd.setDependsOn(StringUtils.tokenizeToStringArray(dependsOn, MULTI_VALUE_ATTRIBUTE_DELIMITERS));
      }

      String autowireCandidate = ele.getAttribute(AUTOWIRE_CANDIDATE_ATTRIBUTE);
      if (isDefaultValue(autowireCandidate)) {
          String candidatePattern = this.defaults.getAutowireCandidates();
          if (candidatePattern != null) {
              String[] patterns = StringUtils.commaDelimitedListToStringArray(candidatePattern);
              bd.setAutowireCandidate(PatternMatchUtils.simpleMatch(patterns, beanName));
          }
      } else {
          bd.setAutowireCandidate(TRUE_VALUE.equals(autowireCandidate));
      }

      if (ele.hasAttribute(PRIMARY_ATTRIBUTE)) {
          bd.setPrimary(TRUE_VALUE.equals(ele.getAttribute(PRIMARY_ATTRIBUTE)));
      }
      
      if (ele.hasAttribute(INIT_METHOD_ATTRIBUTE)) {
          String initMethodName = ele.getAttribute(INIT_METHOD_ATTRIBUTE);
          bd.setInitMethodName(initMethodName);
      }  else if (this.defaults.getInitMethod() != null) {
          bd.setInitMethodName(this.defaults.getInitMethod());
          bd.setEnforceInitMethod(false);
      }

      if (ele.hasAttribute(DESTROY_METHOD_ATTRIBUTE)) {
          String destroyMethodName = ele.getAttribute(DESTROY_METHOD_ATTRIBUTE);
          bd.setDestroyMethodName(destroyMethodName);
      } else if (this.defaults.getDestroyMethod() != null) {
          bd.setDestroyMethodName(this.defaults.getDestroyMethod());
          bd.setEnforceDestroyMethod(false);
      }

      if (ele.hasAttribute(FACTORY_METHOD_ATTRIBUTE)) {
          bd.setFactoryMethodName(ele.getAttribute(FACTORY_METHOD_ATTRIBUTE));
      }
      if (ele.hasAttribute(FACTORY_BEAN_ATTRIBUTE)) {
          bd.setFactoryBeanName(ele.getAttribute(FACTORY_BEAN_ATTRIBUTE));
      }
      return bd;
  }

上面这段代码比较简单,通过Element获取到每个属性值,再设置到BeanDefinition实例中,最后返回该BeanDefinition。 再来看解析子标签的几个方法:

// 解析meta子标签:<meta key="key1" value="固定值"/>
public void parseMetaElements(Element ele, BeanMetadataAttributeAccessor attributeAccessor) {
    NodeList nl = ele.getChildNodes();
    for (int i = 0; i < nl.getLength(); i++) {
        Node node = nl.item(i);
        if (isCandidateElement(node) && nodeNameEquals(node, META_ELEMENT)) {
            Element metaElement = (Element) node;
            
            // 分别获取key与value的元素值
            String key = metaElement.getAttribute(KEY_ATTRIBUTE);
            String value = metaElement.getAtBeanMetadataAttribute实例中tribute(VALUE_ATTRIBUTE);
            
            // 将key与value封装到attribute中
            // 留意BeanMetadataAttribute其实是AbstractBeanDefinition的父类,
            BeanMetadataAttribute attribute = new BeanMetadataAttribute(key, value);
            attribute.setSource(extractSource(metaElement));
            
            // 这里实际上是添加到了BeanMetadataAttribute的父类:AttributeAccessorSupport的attributes属性中了
            // 该属性定义如下:private final Map<String, Object> attributes = new LinkedHashMap<>();
            // 是一个map类型, 可以方便的对元数据进行扩展
            attributeAccessor.addMetadataAttribute(attribute);
        }
    }
}

// 解析lockup-method子标签:<lookup-method name="方法名" bean="bean名称"/>
public void parseLookupOverrideSubElements(Element beanEle, MethodOverrides overrides) {
    NodeList nl = beanEle.getChildNodes();
    for (int i = 0; i < nl.getLength(); i++) {
        Node node = nl.item(i);
        // 循环所有的子标签, 找出其中的lockup-method标签
        if (isCandidateElement(node) && nodeNameEquals(node, LOOKUP_METHOD_ELEMENT)) {
            Element ele = (Element) node;
            String methodName = ele.getAttribute(NAME_ATTRIBUTE);
            String beanRef = ele.getAttribute(BEAN_ELEMENT);
            // 封装成LookupOverride对象
            LookupOverride override = new LookupOverride(methodName, beanRef);
            override.setSource(extractSource(ele));
            // 加入到Set集合中
            overrides.addOverride(override);
        }
    }
}

// 解析replaced-method子标签:
//     <replaced-method name="方法名" replacer="bean名称">
// 	       <arg-type>参数类型,用于确认唯一的方法</arg-type>
//     </replaced-method>
public void parseReplacedMethodSubElements(Element beanEle, MethodOverrides overrides) {
    NodeList nl = beanEle.getChildNodes();
    for (int i = 0; i < nl.getLength(); i++) {
        Node node = nl.item(i);
        if (isCandidateElement(node) && nodeNameEquals(node, REPLACED_METHOD_ELEMENT)) {
            Element replacedMethodEle = (Element) node;
            String name = replacedMethodEle.getAttribute(NAME_ATTRIBUTE);
            String callback = replacedMethodEle.getAttribute(REPLACER_ATTRIBUTE);
            ReplaceOverride replaceOverride = new ReplaceOverride(name, callback);
            // Look for arg-type match elements.
            List<Element> argTypeEles = DomUtils.getChildElementsByTagName(replacedMethodEle, ARG_TYPE_ELEMENT);
            for (Element argTypeEle : argTypeEles) {
                String match = argTypeEle.getAttribute(ARG_TYPE_MATCH_ATTRIBUTE);
                match = (StringUtils.hasText(match) ? match : DomUtils.getTextValue(argTypeEle));
                if (StringUtils.hasText(match)) {
                    replaceOverride.addTypeIdentifier(match);
                }
            }
            replaceOverride.setSource(extractSource(replacedMethodEle));
            overrides.addOverride(replaceOverride);
        }
    }
}

// 解析property子标签:<property name="key1" value="固定值" ref="beanRef"/>
public void parsePropertyElements(Element beanEle, BeanDefinition bd) {
    NodeList nl = beanEle.getChildNodes();
    for (int i = 0; i < nl.getLength(); i++) {
        Node node = nl.item(i);
        if (isCandidateElement(node) && nodeNameEquals(node, PROPERTY_ELEMENT)) {
            parsePropertyElement((Element) node, bd);
        }
    }
}

至此,BeanDefinition的解析与封装就介绍完了,最后我们再看一下Spring是如何完成BeanDefinition注册的。先回顾一下代码:

来看processBeanDefinition()方法:

# DefaultBeanDefinitionDocumentReader
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
    // 通过代理解析Element节点
    BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
    if (bdHolder != null) {
        // 对bean标签解析出来的BeanDefinition进行装饰
        // 用的很少,但此处的是个spi很重要
        bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
        try {
            // 上面解析完成后, 这里开始进行注册BeanDefinition
            BeanDefinitionReaderUtils.registerBeanDefinition这个静态方法:
            (bdHolder, getReaderContext().getRegistry());
        }
        catch (BeanDefinitionStoreException ex) {
            getReaderContext().error("Failed to register bean definition with name '" +
                    bdHolder.getBeanName() + "'", ele, ex);
        }
        // Send registration event.
        getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
    }
}

来看一下registerBeanDefinition这个静态方法:

  # BeanDefinitionReaderUtils
  public static void registerBeanDefinition(
          BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
          throws BeanDefinitionStoreException {
      // Register bean definition under primary name.
      String beanName = definitionHolder.getBeanName();

      // 当基于纯注解的方式启动, 且传入的参数是class时, 会在这里调用BeanDefinition的注册
      // 基于自定义标签元素处理时, 也会进入该方法, 进行BeanDefinition的注册
      registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());

      // Register aliases for bean name, if any.
      String[] aliases = definitionHolder.getAliases();
      if (aliases != null) {
          for (String alias : aliases) {
              registry.registerAlias(beanName, alias);
          }
      }
  }

继续跟踪registerBeanDefinition方法,Spring中bean的描述是通过BeanDefinition来完成的,而BeanDefinition的基本操作是通过BeanDefinitionRegistry这个策略接口来完成的,基本操作主要为:注册BeanDefinition、移除BeanDefinition、获取BeanDefinition等。代码如下:

public interface BeanDefinitionRegistry extends AliasRegistry {
	void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
			throws BeanDefinitionStoreException;

	void removeBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;

	BeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;

	boolean containsBeanDefinition(String beanName);

	String[] getBeanDefinitionNames();

	int getBeanDefinitionCount();

	boolean isBeanNameInUse(String beanName);
}

有关策略的几个接口,这里给一张图,简单说明:

继续来看BeanDefinition的注册。

# DefaultListableBeanFactory
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
			throws BeanDefinitionStoreException {

    Assert.hasText(beanName, "Bean name must not be empty");
    Assert.notNull(beanDefinition, "BeanDefinition must not be null");

    if (beanDefinition instanceof AbstractBeanDefinition) {
        try {
            ((AbstractBeanDefinition) beanDefinition).validate();
        } catch (BeanDefinitionValidationException ex) {
            throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
                    "Validation of bean definition failed", ex);
        }
    }

    BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
    if (existingDefinition != null) {
        // 处理BeanDefinition重复的问题, 前面已经说过了
        if (!isAllowBeanDefinitionOverriding()) {
            throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);
        } else if (existingDefinition.getRole() < beanDefinition.getRole()) {
            // 仅仅记录日志
        } else if (!beanDefinition.equals(existingDefinition)) {
            // 仅仅记录日志
        } else {
            // 仅仅记录日志
        }
        this.beanDefinitionMap.put(beanName, beanDefinition);
    }
    else {
        if (hasBeanCreationStarted()) {
            // Cannot modify startup-time collection elements anymore (for stable iteration)
            // 为什么if这里加了同步锁,而下面的else没有加锁呢?
            synchronized (this.beanDefinitionMap) {
                // 默认是不会进入这个分支的,那什么时候会进入到这个里面呢?
                // 通过@Configuration的方式注入一个BeanDefinition的时候, 其实是可以进入这里的
                // 在parse()和validate()结束之后, 调用loadBeanDefinitions()时, 会进入if里
                this.beanDefinitionMap.put(beanName, beanDefinition);
                
                // 现有容量加1, 即刚好是完成BeanDefinition注册,所需要的容量
                List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
                //加入所有已注册的beanDefinitionName
                updatedDefinitions.addAll(this.beanDefinitionNames);
                
                //加入本次注册的beanName
                updatedDefinitions.add(beanName);
                
                //更新已注册的beanDefinitionName列表为, 这里操作为什么要这么的麻烦?其实是为了节省内存空间,有兴趣可以阅读ArrayList的源码
                this.beanDefinitionNames = updatedDefinitions;
                removeManualSingletonName(beanName);
            }
        } else {
            // 在loadBeanDefinition的过程中, 将beanDefinition添加到map和list集合中, 即赋值
            // 默认在此处完成了真正的bean定义信息的注册工作
            this.beanDefinitionMap.put(beanName, beanDefinition);
            this.beanDefinitionNames.add(beanName);
            removeManualSingletonName(beanName);
        }
        this.frozenBeanDefinitionNames = null;
    }

    if (existingDefinition != null || containsSingleton(beanName)) {
        resetBeanDefinition(beanName);
    } else if (isConfigurationFrozen()) {
        clearByTypeCache();
    }
}

这里完成了BeanDefinition的注册,即将BeanDefinition分别放入beanDefinitionMap、beanDefinitionNames这两个属性中,来看下这两个变量的定义:

private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);
private volatile List<String> beanDefinitionNames = new ArrayList<>(256);

当正真开始进行Bean实例化的时候,就需要使用到这两个变量了。这里等到写生命周期和实例化的时候,再探讨吧。

至此,Spring中BeanDefinition的注册就完成了。关于最后的事件通知,本篇亦不作说明。

文章的最后,我们再来思考一个问题,在上面最后一个方法中,if里加了锁,而else却没有加锁?代码如下:

      // ................................

      if (hasBeanCreationStarted()) {
          // Cannot modify startup-time collection elements anymore (for stable iteration)
          // 为什么if这里加了同步锁,而下面的else没有加锁呢?
          synchronized (this.beanDefinitionMap) {
              // 注册BeanDefinition
          }
      } else {
          //
          // 注册BeanDefinition
      }

      // ................................

我们分析一下:
if中的hasBeanCreationStarted()是用来判断Spring是否正在进行实例化操作。当Spring早实例化Bean的时候,会将该beanName存入alreadyCreated这个Set集合中,整个Bean实例化完成之后,又会清空这个Set集合。 因此进入该if,即表示alreadyCreated不为空,表示Spring正在进行Bean的实例化操作。而Spring的实例化过程是需要遍历beanDefinitionNames集合,这个集合如前文所说,是一个非线程安全的ArrayList类型。
这么说有点啰嗦了,其实就是在遍历的过程中,还想往里面添加,这就会导致线程不安全了。 那什么时候会出现这种情况?给出一个连接,有兴趣的同学可以自己研究:

zhuanlan.zhihu.com/p/30070328

总结

最后,本篇主要介绍了Resource、BeanDefinition的基本概念、以及默认标签的解析处理、BeanDefinition的注册操作。我们用一张流程图来总结本篇文档

文末

本篇的文末,给出前面的parent和abstract的用法参考: 定义两个类

@Data
public class MyChild {
	private String name;
	private int age;
	private String address;
}
@Data
public class MyParent {
	private String name;
	private String address;
}

xml文件如下:parent其实是一个模板,可以有包括child1、child2等等来引用该模板中的相同属性值。简单起见这里,只给出了一个

spring.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="parent" abstract="true" class="MyParent">
		<property name="name" value="老子"></property>
		<property name="address" value="江苏二"></property>
	</bean>

	<bean id="child" parent="parent" class="MyChild">
		<property name="name" value="儿子"></property>
		<property name="age" value="20"></property>
	</bean>
</beans>

测试类如下:

public class App {
  public static void main(String[] args) {
      ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring.xml");
      MyChild child = (MyChild) context.getBean("child");
      System.out.println(child);
  }
}

程序输出:

MyChild{name='儿子', age=20, address='江苏二'},可以看出,child的address属性的值,其实是"继承"自 xml中的"父类",当公共属性值较多时,该方式就会比较有效。使用比较简单,但其设计思路,很值得我们借鉴,通常我们会将多个类继承(extends)该类(java中的继承),而Spring则是通过父子类的属性替换实现的。