Fork me on GitHub

Spring读书笔记——bean解析

前情回顾

上篇《Spring读书笔记——bean加载》我们从代码角度介绍了有哪些类负责解析XML文件,又是如何一步步从XML格式脱变成我们熟悉的bean的,直到DefaultBeanDefinitionDocumentReader的registerBeanDefinitions方法。我们抽丝剥茧,终于快看到Spring是如何解析bean的代码了。
在此之前,我们回一下上篇看到过的主要类和方法
XmlBeanFactory(XmlFactory) ->
XmlBeanDefinitionReader(loadBeanDefinitions)->
XmlBeanDefinitionReader(doLoadBeanDefinitions)->
XmlBeanDefintionReader(registerBeanDefinitions)
DefaultBeanDefinitionDocumentReader(registerBeanDefinitions)->
DefaultBeanDefinitonDocumentReader(doRegisterBeanDefinitions)

解析的类型

Spring的标签分为默认标签和自定义标签两种。
默认标签主要包括:import、alias、bean和beans
自定义标签:顾名思义就是自定义的标签,比如

DefaultBeanDefinitionDocumentReader

从XmlBeanDefinitionReader的registerBeanDefinitions开始,我们第一步要做的就是创建一个Xml文档的解析类

BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();

createBeanDefinitionDocumentReader方法创建了类DefaultBeanDefinitionDocumentReader。
从DefaultBeanDefinitionDocumentReader的registerBeanDefinitions到doRegisterBeanDefinitions,下面我们着重从doRegisterBeanDefinitions的parseBeanDefintions方法开始往下看。

代理类BeanDefinitionParserDelegate

负责解析bean的并不是DefaultBeanDefinitionDocumentReader,而是委托给了代理类BeanDefinitionParserDelegate,后面我们会看到BeanDefinitionParserDelegate这个功臣是如何施展拳脚解析bean的。

parseBeanDefinitions(root, this.delegate);

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

  • 判断是否是默认命名空间的,如果是则遍历root下所有的子标签,如果不是,则进行自定义解析
  • 对于是默认命名空间下的标签,在遍历得到每一个子标签后开始调用parseDefaultElement方法开始我们之前提到的默认标签的解析。

默认标签解析

前面已经提到过默认标签有四种,我们看看代码也是一目了然,下面是解析默认标签的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...else语句分别判定了import、alias、bean和beans的情况,并且不同情况作了不同的方法实现
  • 首先要看的就是我们再熟悉不过的bean标签的解析processBeanDefinition方法

bean标签的解析

processBeanDefinition的方法实现如下

protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
   BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
   if (bdHolder != null) {
      bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
      try {
         // Register the final decorated instance.
         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));
   }
}

方法并不复杂,大致看下主要干了下面几件事

  • 前面介绍的BeanDefinitionParserDelegate开始排上用场,用于对bean元素解析,并得到BeanDefinitionHolder类型的bdHolder对象
  • 对于解析得到结果不为空的情况,首先还要对于需要修饰的情况(默认标签包含自定义标签)进行处理
  • 然后借助BeanDefinitionReaderUtils的registerBeanDefinition对bdHolder进行注册
  • 最后发送响应事件,通知响应的监听器(Spring3x版本这块还没有具体实现,只是留了个壳)

delegate.parseBeanDefinitionElement
我们主要从processBeanDefintion的第一行代码开始,来到BeanDefinitionParserDelegate.parseBeanDefinitionElement

public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) {
   String id = ele.getAttribute(ID_ATTRIBUTE);
   String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);

   List<String> aliases = new ArrayList<String>();
   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 (logger.isDebugEnabled()) {
         logger.debug("No XML 'id' specified - using '" + beanName +
               "' as bean name and " + aliases + " as aliases");
      }
   }

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

   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);
               // Register an alias for the plain bean class name, if still possible,
               // if the generator returned the class name plus a suffix.
               // This is expected for Spring 1.2/2.0 backwards compatibility.
               String beanClassName = beanDefinition.getBeanClassName();
               if (beanClassName != null &&
                     beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&
                     !this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
                  aliases.add(beanClassName);
               }
            }
            if (logger.isDebugEnabled()) {
               logger.debug("Neither XML 'id' nor 'name' specified - " +
                     "using generated bean name [" + beanName + "]");
            }
         }
         catch (Exception ex) {
            error(ex.getMessage(), ele);
            return null;
         }
      }
      String[] aliasesArray = StringUtils.toStringArray(aliases);
      return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
   }

   return null;
}

  • 有很多我们之前熟知的细节从这里的代码就可以找到根据,比如“bean如果不声明name属性,spring会默认创建一个name”,显然通过上面一点代码我们就看到Spring确实是这么实现的,而且我们看到如果没有声明name,默认的name值就是id的值
  • 紧接着,我们看下
    AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);这行代码,进入parseBeanDefinitionElement我们会看到这个方法做了很多的解析的工作,比如解析parent属性、解析元数据、解析构造函数、解析property子元素等等

parseBeanDefinitionElement方法

public AbstractBeanDefinition parseBeanDefinitionElement(
      Element ele, String beanName, BeanDefinition containingBean) {

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

   String className = null;
   if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
      className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
   }

   try {
      String parent = null;
      if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
         parent = ele.getAttribute(PARENT_ATTRIBUTE);
      }
      AbstractBeanDefinition bd = createBeanDefinition(className, parent);

      parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
      bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));

      parseMetaElements(ele, bd);
      parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
      parseReplacedMethodSubElements(ele, bd.getMethodOverrides());

      parseConstructorArgElements(ele, bd);
      parsePropertyElements(ele, bd);
      parseQualifierElements(ele, bd);

      bd.setResource(this.readerContext.getResource());
      bd.setSource(extractSource(ele));

      return bd;
   }
   catch (ClassNotFoundException ex) {
      error("Bean class [" + className + "] not found", ele, ex);
   }
   catch (NoClassDefFoundError err) {
      error("Class that bean class [" + className + "] depends on not found", ele, err);
   }
   catch (Throwable ex) {
      error("Unexpected failure during bean definition parsing", ele, ex);
   }
   finally {
      this.parseState.pop();
   }

   return null;
}

我们挑其中一个解析属性函数parseConstructorArgElements看下具体实现。
parseConstructorArgElements用于解析ConstructArg标签。
我们常见的construct-arg大概是这样

<bean id="user" class="com.jackie.User">
    ​<construct-arg index="0">
    ​    ​<value>jackie</value>
    ​</construct-arg>
    ​<construct-arg index="1">
        <value>male</value>
    </construct-arg>
</bean>

construct-arg标签的解析过程如下所示

public void parseConstructorArgElement(Element ele, BeanDefinition bd) {
   String indexAttr = ele.getAttribute(INDEX_ATTRIBUTE);
   String typeAttr = ele.getAttribute(TYPE_ATTRIBUTE);
   String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
   if (StringUtils.hasLength(indexAttr)) {
      try {
         int index = Integer.parseInt(indexAttr);
         if (index < 0) {
            error("'index' cannot be lower than 0", ele);
         }
         else {
            try {
               this.parseState.push(new ConstructorArgumentEntry(index));
               Object value = parsePropertyValue(ele, bd, null);
               ConstructorArgumentValues.ValueHolder valueHolder = new ConstructorArgumentValues.ValueHolder(value);
               if (StringUtils.hasLength(typeAttr)) {
                  valueHolder.setType(typeAttr);
               }
               if (StringUtils.hasLength(nameAttr)) {
                  valueHolder.setName(nameAttr);
               }
               valueHolder.setSource(extractSource(ele));
               if (bd.getConstructorArgumentValues().hasIndexedArgumentValue(index)) {
                  error("Ambiguous constructor-arg entries for index " + index, ele);
               }
               else {
                  bd.getConstructorArgumentValues().addIndexedArgumentValue(index, valueHolder);
               }
            }
            finally {
               this.parseState.pop();
            }
         }
      }
      catch (NumberFormatException ex) {
         error("Attribute 'index' of tag 'constructor-arg' must be an integer", ele);
      }
   }
   else {
      try {
         this.parseState.push(new ConstructorArgumentEntry());
         Object value = parsePropertyValue(ele, bd, null);
         ConstructorArgumentValues.ValueHolder valueHolder = new ConstructorArgumentValues.ValueHolder(value);
         if (StringUtils.hasLength(typeAttr)) {
            valueHolder.setType(typeAttr);
         }
         if (StringUtils.hasLength(nameAttr)) {
            valueHolder.setName(nameAttr);
         }
         valueHolder.setSource(extractSource(ele));
         bd.getConstructorArgumentValues().addGenericArgumentValue(valueHolder);
      }
      finally {
         this.parseState.pop();
      }
   }
}

  • 如果constrcut-arg没有指定index函数,默认按照声明属性的顺序来解析,如果声明则按照index的值解析
  • 使用ConstrcutorArgumentValues的ValueHolder来封装解析得到的type、name等属性值
  • 最后将上面的valueHolder对象按照index添加到BeanDefinition的indexedArgument属性上
  • 注意,对于没有index声明的,最终解析后是存放到genericArgumentValues属性上

bean标签的注册

上面层层剥开,一直到解析到具体的属性,我们现在再一层层的往回走。前面所做的工作,无非是将Xml的元素解析出来并与Spring中的BeanDefinition,也就是换了一种形式在Spring中存在。
我们最终得到的是BeanDefinitionHolder

public BeanDefinitionHolder(BeanDefinition beanDefinition, String beanName, String[] aliases) {
   Assert.notNull(beanDefinition, "BeanDefinition must not be null");
   Assert.notNull(beanName, "Bean name must not be null");
   this.beanDefinition = beanDefinition;
   this.beanName = beanName;
   this.aliases = aliases;
}

解析转换工作完成了,现在需要开始bean的注册了

我们回到DefaultBeanDefinitionDocumentReader的processBeanDefinition,看看注册是如何实现的。
从DefaultBeanDefinitionDocumentReader.processBeanDefinition->BeanDefinitionReaderUtils.registerBeanDefinition->DefaultLisableBeanFactory.registerBeanFactory。

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

   synchronized (this.beanDefinitionMap) {
      Object oldBeanDefinition = this.beanDefinitionMap.get(beanName);
      if (oldBeanDefinition != null) {
         if (!this.allowBeanDefinitionOverriding) {
            throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
                  "Cannot register bean definition [" + beanDefinition + "] for bean '" + beanName +
                  "': There is already [" + oldBeanDefinition + "] bound.");
         }
         else {
            if (this.logger.isInfoEnabled()) {
               this.logger.info("Overriding bean definition for bean '" + beanName +
                     "': replacing [" + oldBeanDefinition + "] with [" + beanDefinition + "]");
            }
         }
      }
      else {
         this.beanDefinitionNames.add(beanName);
         this.frozenBeanDefinitionNames = null;
      }
      this.beanDefinitionMap.put(beanName, beanDefinition);
   }

   resetBeanDefinition(beanName);
}
  • 如果beanName已经注册又没有声明可以覆盖,则会抛出异常
  • 符合条件的bean最终会被加入一个map结合
  • map集合的key是beanName,value就是前面解析得到的BeanDefinition实例对象
  • 清楚解析之前留下对应的beanName的缓存

至此,我们看到了Spring是如何解析标签(我们还有自定义标签没说),如何解析bean标签(我们还有import、alias和beans没说),如何解析标签属性(我们还有太多的属性没有介绍,但是都是类似的过程),最终完成了bean的注册。

如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮,您的“推荐”将是我最大的写作动力!如果您想持续关注我的文章,请扫描二维码,关注JackieZheng的微信公众号,我会将我的文章推送给您,并和您一起分享我日常阅读过的优质文章。

posted @ 2017-10-15 22:48  JackieZheng  阅读(1098)  评论(0编辑  收藏  举报