天天看點

Spring IOC 源碼分析-bean标簽解析概述重點關系圖代碼流程分析

概述

Spring ioc 解析配置檔案過程中,核心的處理就是解析bean标簽

重點關系圖

bean标簽定義實體類- 在記憶體中的映射和繼承關系圖

Spring IOC 源碼分析-bean标簽解析概述重點關系圖代碼流程分析

這裡的AbstractBeanDefinition 實作了對bean标簽的預設初始化配置,字段中儲存了bean的大部分通用屬性,比如scope,abstract,autoware等bean标簽屬性

bean解析類-用于解析bean标簽的代理工具類

Spring IOC 源碼分析-bean标簽解析概述重點關系圖代碼流程分析

BeanDefinitionParserDelegate就是預設bean解析器,主要用于解析bean标簽 傳回BeanDefinitionHolder,BeanDefinitionHolder實際上是一個包裝類,用于裝載解析後的bean,其中包括唯一性名稱beanName和所有别名aliases數組,還有解析完成後的beanDefinition執行個體

代碼流程分析

//解析bean标簽的入口,ele為bean标簽根節點元素(在前邊已經通過document解析器讀取xml配置檔案并分離出Element(bean)元素,containingBean為嵌入beanDefinition,預設為null)
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {

//提取id屬性
String id = ele.getAttribute(ID_ATTRIBUTE);
//提取name屬性
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));
}

String beanName = id;
if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
   beanName = aliases.remove();
   if (logger.isDebugEnabled()) {
      logger.debug("No XML 'id' specified - using '" + beanName +
            "' as bean name and " + aliases + " as aliases");
   }
}

//堅持beanName在全局名稱中是否唯一
if (containingBean == null) {
   checkNameUniqueness(beanName, aliases, ele);
}
           
//正式開始解析ele
AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
           

進入parseBeanDefinitionElement方法

//把目前解析的bean名稱封裝成實體入棧,友善後續日志輸出(
this.parseState.push(new BeanEntry(beanName));

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

try {
//調用工具類建立GenericBeanDefinition實體,并根據參數初始化
   AbstractBeanDefinition bd = createBeanDefinition(className, parent);

  //解析bean屬性 封裝到bd中,這個方法就是對bean标簽中的各個屬性,比如lazy_init,scope,singleton等進行解析并封裝,邏輯很簡單,有興趣的可以自行閱讀源碼,這裡不再做詳述
   parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
   bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));

   //解析meta标簽 
   parseMetaElements(ele, bd);
   //解析 lookup标簽
   parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
   // 解析replace标簽
   parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
   //解析constructor标簽
   parseConstructorArgElements(ele, bd);

   //解析property标簽
   parsePropertyElements(ele, bd);
   parseQualifierElements(ele, bd);

   //設定資源(從context中擷取目前資源)
   bd.setResource(this.readerContext.getResource());
   bd.setSource(extractSource(ele));

   return bd;
}
catch (ClassNotFoundException ex) {
//輸出parseState中目前bean資訊
   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();
}
           

這裡重點分析一下對property屬性的分析,因為平時用的最多的就是這個子元素标簽

進入parsePropertyElement這個方法

//解析property标簽
public void parsePropertyElement(Element ele, BeanDefinition bd) {

    //提取property中的唯一性辨別name屬性(跟bean标簽流程類似)
   String propertyName = ele.getAttribute(NAME_ATTRIBUTE);
   if (!StringUtils.hasLength(propertyName)) {
      error("Tag 'property' must have a 'name' attribute", ele);
      return;
   }
   this.parseState.push(new PropertyEntry(propertyName));
   try {

      if (bd.getPropertyValues().contains(propertyName)) {
         error("Multiple 'property' definitions for property '" + propertyName + "'", ele);
         return;
      }
     //解析property中 value屬性和ref屬性
      Object val = parsePropertyValue(ele, bd, propertyName);
      PropertyValue pv = new PropertyValue(propertyName, val);

      parseMetaElements(ele, pv);
      pv.setSource(extractSource(ele));
      bd.getPropertyValues().addPropertyValue(pv);
   }
   finally {
      this.parseState.pop();
   }
}
           

進入parsePropertyValue ,注意思路跟上,不要斷,這是最後一步分析,也是相對比較複雜的邏輯

public Object parsePropertyValue(Element ele, BeanDefinition bd, @Nullable String propertyName) {
   String elementName = (propertyName != null) ?
               "<property> element for property '" + propertyName + "'" :
               "<constructor-arg> element";

   // Should only have one child element: ref, value, list, etc.
//提起property屬性的子元素比如 list ref等
   NodeList nl = ele.getChildNodes();
   Element subElement = null;
   for (int i = ; i < nl.getLength(); i++) {
      Node node = nl.item(i);
      if (node instanceof Element && !nodeNameEquals(node, DESCRIPTION_ELEMENT) &&
            !nodeNameEquals(node, META_ELEMENT)) {
         // Child element is what we're looking for.
         if (subElement != null) {
            error(elementName + " must not contain more than one sub-element", ele);
         }
         else {
            subElement = (Element) node;
         }
      }
   }

    //解析ref屬性
   boolean hasRefAttribute = ele.hasAttribute(REF_ATTRIBUTE);
    //解析value屬性
   boolean hasValueAttribute = ele.hasAttribute(VALUE_ATTRIBUTE);


   if ((hasRefAttribute && hasValueAttribute) ||
         ((hasRefAttribute || hasValueAttribute) && subElement != null)) {
      error(elementName +
            " is only allowed to contain either 'ref' attribute OR 'value' attribute OR sub-element", ele);
   }

//解析ref屬性
   if (hasRefAttribute) {
      String refName = ele.getAttribute(REF_ATTRIBUTE);
      if (!StringUtils.hasText(refName)) {
         error(elementName + " contains empty 'ref' attribute", ele);
      }
      RuntimeBeanReference ref = new RuntimeBeanReference(refName);
      ref.setSource(extractSource(ele));
      return ref;
   }

//解析value屬性
   else if (hasValueAttribute) {
      TypedStringValue valueHolder = new TypedStringValue(ele.getAttribute(VALUE_ATTRIBUTE));
      valueHolder.setSource(extractSource(ele));
      return valueHolder;
   }
//解析子元素
   else if (subElement != null) {
      return parsePropertySubElement(subElement, bd);
   }
   else {
      // Neither child element nor "ref" or "value" attribute found.
      error(elementName + " must specify a ref or value", ele);
      return null;
   }
}
           

這裡主要流程分為五部分

  1. 清單内容 解析出子元素 ref,value,bean,list map等
    Spring IOC 源碼分析-bean标簽解析概述重點關系圖代碼流程分析
  2. 判斷是否同時擁有ref屬性和value屬性 或存在ref屬性或者value屬性,但是又子元素,用這樣的規則來檢驗正确性,其實就是關于xml schema中的設定進行檢驗
  3. 解析ref屬性 ,用RuntimeBeanReference封裝
  4. 解析value屬性,用TypedStringValue封裝
  5. 子元素處理 ,比如進入子元素處理邏輯 parsePropertySubElement
    Spring IOC 源碼分析-bean标簽解析概述重點關系圖代碼流程分析
if (!isDefaultNamespace(ele)) {
   return parseNestedCustomElement(ele, bd);
}
else if (nodeNameEquals(ele, BEAN_ELEMENT)) {
   BeanDefinitionHolder nestedBd = parseBeanDefinitionElement(ele, bd);
   if (nestedBd != null) {
      nestedBd = decorateBeanDefinitionIfRequired(ele, nestedBd, bd);
   }
   return nestedBd;
}
else if (nodeNameEquals(ele, REF_ELEMENT)) {
   // A generic reference to any name of any bean.
   String refName = ele.getAttribute(BEAN_REF_ATTRIBUTE);
   boolean toParent = false;
   if (!StringUtils.hasLength(refName)) {
      // A reference to the id of another bean in a parent context.
      refName = ele.getAttribute(PARENT_REF_ATTRIBUTE);
      toParent = true;
      if (!StringUtils.hasLength(refName)) {
         error("'bean' or 'parent' is required for <ref> element", ele);
         return null;
      }
   }
   if (!StringUtils.hasText(refName)) {
      error("<ref> element contains empty target attribute", ele);
      return null;
   }
   RuntimeBeanReference ref = new RuntimeBeanReference(refName, toParent);
   ref.setSource(extractSource(ele));
   return ref;
}
else if (nodeNameEquals(ele, IDREF_ELEMENT)) {
   return parseIdRefElement(ele);
}
else if (nodeNameEquals(ele, VALUE_ELEMENT)) {
   return parseValueElement(ele, defaultValueType);
}
else if (nodeNameEquals(ele, NULL_ELEMENT)) {
   // It's a distinguished null value. Let's wrap it in a TypedStringValue
   // object in order to preserve the source location.
   TypedStringValue nullHolder = new TypedStringValue(null);
   nullHolder.setSource(extractSource(ele));
   return nullHolder;
}
else if (nodeNameEquals(ele, ARRAY_ELEMENT)) {
   return parseArrayElement(ele, bd);
}
else if (nodeNameEquals(ele, LIST_ELEMENT)) {
   return parseListElement(ele, bd);
}
else if (nodeNameEquals(ele, SET_ELEMENT)) {
   return parseSetElement(ele, bd);
}
else if (nodeNameEquals(ele, MAP_ELEMENT)) {
   return parseMapElement(ele, bd);
}
else if (nodeNameEquals(ele, PROPS_ELEMENT)) {
   return parsePropsElement(ele);
}
else {
   error("Unknown property sub-element: [" + ele.getNodeName() + "]", ele);
   return null;
}
           

這裡就是對property各種子元素進行處理的邏輯,請自行分析,不再詳述

以上就是對bean标簽解析的全過程,不知道有沒有蒙圈,下邊通過一張活動圖來梳理一下主要的解析步驟

解析活動圖

Spring IOC 源碼分析-bean标簽解析概述重點關系圖代碼流程分析

繼續閱讀