文章出處

前情回顧

上篇《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的微信公眾號,我會將我的文章推送給您,并和您一起分享我日常閱讀過的優質文章。


文章列表


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()