天天看點

三、Spring源碼分析:IOC-預設标簽的解析Spring源碼分析:預設标簽的解析

Spring源碼分析:預設标簽的解析

文章目錄

  • Spring源碼分析:預設标簽的解析
    • bean标簽的解析及注冊
      • 解析BeanDefinition
        • 建立用于屬性承載的BeanDefinition
        • 解析各種屬性
        • 解析子元素meta
        • 解析子元素lookup-method
        • 解析子元素replaced-method
        • 解析子元素constructor-arg(構造方法注入)
        • 解析子元素perperty
        • 解析子元素qualifier
      • AbstractBeanDefinition屬性
      • 解析預設标簽中的自定義标簽元素
      • 注冊解析的BeanDefinition
        • 通過beanName注冊BeanDefinition
        • 通過别名注冊BeanDefinition
      • 通知監聽器解析及注冊完成
    • alias标簽的處理
    • import标簽的解析
    • 嵌入式beans标簽的解析

​ 書接上回,提前提到Spring中的标簽和自定義标簽兩種,而這兩種标簽的用法以及解析方式存在着很大的不同,本文重點分析預設标簽的解析過程。

​ 預設标簽的解析是在parseDefaultElement方法中進行,方法的邏輯清晰明了(原來像Spring這麼牛逼的代碼也用這麼多if else),這裡分别對四種不同标簽(import、alias、bean和beans)做了不同的處理。

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

bean标簽的解析及注冊

顯然,這四種标簽裡,bean标簽的解析最為重要,但是也是最複雜的,那麼我們首先探索一下bean标簽的解析。我們步入processBeanDefinition()方法:

protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
   BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
   if (bdHolder != null) {
      bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
      try {
         //注冊最終裝飾的執行個體。
    BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
      }
      catch (BeanDefinitionStoreException ex) {
         getReaderContext().error("Failed to register bean definition with name '" +
               bdHolder.getBeanName() + "'", ele, ex);
      }
      // 發送注冊事件.
      getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
   }
}
           

總結一下大緻的邏輯:

  • 首先委托BeanDefinitionDelegate類的parseBeanDefinitionElement方法進行元素解析,傳回BeanDefinitionHolder類型的執行個體bdHolder,經過這個方法後,bdHolder執行個體已經包含我們配置檔案中配置的各種屬性了,例如class、name、id、alias之類的屬性。
  • 當傳回的bdHolder不為空的情況下若存在預設标簽的子節點下再有自定義屬性,還需要再次對自定義标簽進行解析。
  • 解析完成後,需要對解析後的bdHolder進行注冊,同樣,注冊操作委托給了BeanDefinitionReaderUtils的registerBeanDefinition方法。
  • 最後發出響應事件,通知相關的監聽器,這個bean已經加載完成了。

換了個軟體畫時序圖(好像畫的好看點了):

三、Spring源碼分析:IOC-預設标簽的解析Spring源碼分析:預設标簽的解析

解析BeanDefinition

接下來我們就對各個操作步驟具體分析,先從元素分析及資訊提取開始,步入第一句:

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

再步入:

@Nullable
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(0);
      if (logger.isTraceEnabled()) {
         logger.trace("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 {
             //如果不存在beanName,那麼根據Spring中提供的命名規則為目前bean生成對應的beanName
            if (containingBean != null) {
               beanName = BeanDefinitionReaderUtils.generateBeanName(
                     beanDefinition, this.readerContext.getRegistry(), true);
            }
            else {
               beanName = this.readerContext.generateBeanName(beanDefinition);
               // 如果生成器傳回了類名加字尾,則仍然可以為普通bean類名注冊一個别名。對于Spring 1.22.0向後相容,這是預期的。
               String beanClassName = beanDefinition.getBeanClassName();
               if (beanClassName != null &&
                     beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&
                     !this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
                  aliases.add(beanClassName);
               }
            }
            if (logger.isTraceEnabled()) {
               logger.trace("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;
}
           

這裡我們隻能看到對屬性id以及name的解析,這段代碼的主要工作主要包括以下内容:

  1. 提取元素中的id以及name屬性。
  2. 進一步解析其他所有屬性并統一封裝至GenericBeanDefinition(AbstractBeanDefinition)執行個體中。
  3. 如果檢測到bean沒有指定beanName,那麼使用預設規則為此Bean生成beanName;
  4. 将擷取到的資訊封裝到BeanDefinitionHolder執行個體中傳回。

我們進一步地檢視步驟2中對标簽其他屬性的解析過程:

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

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

   String className = null;
    //解析class屬性
   if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
      className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
   }
   String parent = null;
    //解析parent屬性
   if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
      parent = ele.getAttribute(PARENT_ATTRIBUTE);
   }

   try {
       //建立GenericBeanDefinition執行個體
      AbstractBeanDefinition bd = createBeanDefinition(className, parent);
	//寫死解析預設bean的各種屬性
      parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
     //提取decription
       bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));
	//解析中繼資料
      parseMetaElements(ele, bd);
       //解析lookup-method
      parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
       //解析replaced-method
      parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
		//解析構造函數參數
      parseConstructorArgElements(ele, bd);
       //解析property子元素
      parsePropertyElements(ele, bd);
       //解析qualifier子元素
      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;
}
           

終于,bean标簽的所有屬性,不論是常用的還是不常用的我們都看到了,盡管有些複雜的屬性還需要進一步的解析,接下來我們繼續。

建立用于屬性承載的BeanDefinition

BeanDefiniton是一個接口,在Spring中存在三種實作(存疑,書上這麼寫的,應該是七種實作):RootBeanDefinition,ChildBeanDefinition以及GenericBeanDefinition。這三種實作均繼承了AbstractBeanDefinition,其中BeanDefiniton是配置檔案袁術标簽在容器中的内部表示形式。标簽擁有class、scope(作用域,Spring有五種作用域:singleton、prototype、session、request、global session,這裡複習一下)、lazy-init等配置屬性,BeanDefinition則提供了響應的beanClass、scope、lazyInit屬性,BeanDefinition和中的屬性是一一對應的。其中RootBeanDefinition是最常用的實作類。

在配置檔案中可以定義父和子,父用RootBeanDefinition表示,而子用ChildBeanDefinition表示,如果沒有夫bean就是用RootBeanDefinition表示。然後AbstractBeanDefinition對兩者共同的類資訊進行抽象。直接上類圖:

三、Spring源碼分析:IOC-預設标簽的解析Spring源碼分析:預設标簽的解析

接下來我們繼續看代碼:

protected AbstractBeanDefinition createBeanDefinition(@Nullable String className, @Nullable String parentName)
      throws ClassNotFoundException {

   return BeanDefinitionReaderUtils.createBeanDefinition(
         parentName, className, this.readerContext.getBeanClassLoader());
}
           

這段代碼用于建立GenericBeanDefinition,繼續步入:

public static AbstractBeanDefinition createBeanDefinition(
      @Nullable String parentName, @Nullable String className, @Nullable ClassLoader classLoader) throws ClassNotFoundException {

   GenericBeanDefinition bd = new GenericBeanDefinition();
   bd.setParentName(parentName);
   if (className != null) {
      if (classLoader != null) {
         bd.setBeanClass(ClassUtils.forName(className, classLoader));
      }
      else {
         bd.setBeanClassName(className);
      }
   }
   return bd;
}
           

為給定的父名稱和類名稱建立一個新的GenericBeanDefinition,如果已指定ClassLoader,則熱切加載bean類。

參數:

parentName –父bean的名稱(如果有)

className – Bean類的名稱(如果有)

classLoader –用于加載Bean類的ClassLoader(可以為null以僅按名稱注冊Bean類)

解析各種屬性

建立了bean資訊的承載執行個體(GenericBeanDefinition)後,便可以解析bean的各種屬性了,我們步入parseBeanDefinitionAttributes(ele, beanName, containingBean, bd)方法,該方法是對element所有元素屬性進行解析。

public AbstractBeanDefinition parseBeanDefinitionAttributes(Element ele, String beanName,
      @Nullable BeanDefinition containingBean, AbstractBeanDefinition bd) {
	//解析scope屬性
    //如果使用了singleton标簽就會報錯
   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) {
       //在嵌入式beanDefinition情況下且沒有單獨指定scope屬性,則使用父類預設的屬性
      // Take default from containing bean in case of an inner bean definition.
      bd.setScope(containingBean.getScope());
   }
	//解析abstract屬性
   if (ele.hasAttribute(ABSTRACT_ATTRIBUTE)) {
      bd.setAbstract(TRUE_VALUE.equals(ele.getAttribute(ABSTRACT_ATTRIBUTE)));
   }
	//解析lazy-init
   String lazyInit = ele.getAttribute(LAZY_INIT_ATTRIBUTE);
   if (isDefaultValue(lazyInit)) {
      lazyInit = this.defaults.getLazyInit();
   }
    //沒有設定,或者設定成其他字元會被設定成false
   bd.setLazyInit(TRUE_VALUE.equals(lazyInit));
	//解析autowire屬性
   String autowire = ele.getAttribute(AUTOWIRE_ATTRIBUTE);
   bd.setAutowireMode(getAutowireMode(autowire));
	//解析depends-on屬性
   if (ele.hasAttribute(DEPENDS_ON_ATTRIBUTE)) {
      String dependsOn = ele.getAttribute(DEPENDS_ON_ATTRIBUTE);
      bd.setDependsOn(StringUtils.tokenizeToStringArray(dependsOn, MULTI_VALUE_ATTRIBUTE_DELIMITERS));
   }
	//解析autowireCandidate屬性 spring <bean>标簽的 autowire-candidate屬性設定為false,容器在查找自動裝配對象時,将不考慮該bean,即該bean不會被作為其它bean自動裝配的候選者,但該bean本身還是可以使用自動裝配來注入其它bean的。
   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));
   }
	//解析primary屬性
   if (ele.hasAttribute(PRIMARY_ATTRIBUTE)) {
      bd.setPrimary(TRUE_VALUE.equals(ele.getAttribute(PRIMARY_ATTRIBUTE)));
   }
	//解析initMethodName屬性
   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);
   }
	//解析destroy-method屬性
   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);
   }
	//解析FactoryMethod屬性
   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;
}
           

一眼看過去全是if,而且好多屬性都沒聽說過,不過代碼還是很清晰的。

解析子元素meta

public void parseMetaElements(Element ele, BeanMetadataAttributeAccessor attributeAccessor) {
   NodeList nl = ele.getChildNodes();
    //擷取目前節點的所有子元素
   for (int i = 0; i < nl.getLength(); i++) {
      Node node = nl.item(i);
       //提取meta
      if (isCandidateElement(node) && nodeNameEquals(node, META_ELEMENT)) {
         Element metaElement = (Element) node;
         String key = metaElement.getAttribute(KEY_ATTRIBUTE);
         String value = metaElement.getAttribute(VALUE_ATTRIBUTE);
          //構造BeanMetaDataAttribute
         BeanMetadataAttribute attribute = new BeanMetadataAttribute(key, value);
         attribute.setSource(extractSource(metaElement));
          //記錄資訊
         attributeAccessor.addMetadataAttribute(attribute);
      }
   }
}
           

這裡的 BeanMetadataAttributeAccessor其實就是GenericBeanDefinition。本人沒有用過meta标簽,用法如下:

<bean id="test" class="com.qust.test.TestBean">
   <meta key="testStr" value="test"/>
</bean>
           

這段代碼不會展現在test這個bean中,是一個額外的聲明,當需要的時候可以用BeanDefinition的getAttribute(key)方法進行擷取。

解析子元素lookup-method

又是一個沒有用過的元素。。lookup-method通常被稱為擷取器注入。spring in action中對它的描述是,一種特殊的方法注入,它是把一個方法聲明為傳回某種類型的bean,而實際要傳回的bean是在配置檔案裡面配置的,可用在設計可插拔的功能上,接觸程式依賴。寫個demo:

public class User {
   public void showMe(){
      System.out.println("i am user");
   }
}
           
public class Teacher extends User{
   @Override
   public void showMe() {
      System.out.println("i am teacher");
   }
}
           
public abstract class GetBeanTest {
   public void showMe(){
      this.getBean().showMe();
   }
   public abstract User getBean();
}
           
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tool"
      xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tool http://www.springframework.org/schema/tool/spring-tool.xsd">
   <bean id="getBeanTest" class="com.qust.test.GetBeanTest">
      <lookup-method name="getBean" bean="teacher"/>
   </bean>
   <bean id="teacher" class="com.qust.test.Teacher"/>
</beans>
           

這就很神奇!Spring執行個體化了一個抽象類,通過這個例子,我們可以看出來lookup-method的作用是給一個抽象類注入一個實體類。

我們看源代碼:

public void parseLookupOverrideSubElements(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, LOOKUP_METHOD_ELEMENT)) {
         Element ele = (Element) node;
         String methodName = ele.getAttribute(NAME_ATTRIBUTE);
         String beanRef = ele.getAttribute(BEAN_ELEMENT);
         LookupOverride override = new LookupOverride(methodName, beanRef);
         override.setSource(extractSource(ele));
         overrides.addOverride(override);
      }
   }
}
           

與parseMetaElements有點像。在資料存儲上面通過LookupOverride類型的實體類來進行資料承載并記錄在AbstractBeanDefinition中的methodOverrides中。

解析子元素replaced-method

又是一個沒有用過的标簽,強烈懷疑自己的水準!首先學習以下這個元素的用法:可以在運作時用新的方法替換現有的方法。與之前的look-up不同的是,replaced-method不但可以動态地替換傳回實體bean,而且還能動态地更改原有方法的邏輯。以下是示例:

public class TestChangeMethod {
   public void changeMe(){
      System.out.println("changeMe");
   }
}
           
public class TestMethodReplacer implements MethodReplacer {
   @Override
   public Object reimplement(Object obj, Method method, Object[] args) throws Throwable {
      System.out.println("我替換了原有的方法");
      return null;
   }
}
           
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tool"
      xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tool http://www.springframework.org/schema/tool/spring-tool.xsd">
   <bean id="testChangeMethod" class="com.qust.test.TestChangeMethod">
      <replaced-method name="changeMe" replacer="replacer"/>
   </bean>
   <bean id="replacer" class="com.qust.test.TestMethodReplacer"/>
</beans>
           

可以替換方法,有動态代理的意思。

看一下源代碼:

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

很像lookup-method,這裡先不進行詳細介紹,在後續的文章中再進行詳細的介紹。

解析子元素constructor-arg(構造方法注入)

對構造函數的解析是非常常用的,同時也是非常複雜的。這裡直接上代碼:

public void parseConstructorArgElements(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, CONSTRUCTOR_ARG_ELEMENT)) {
         parseConstructorArgElement((Element) node, bd);
      }
   }
}
           

這個結構似乎我們可以想象到,周遊所有子元素,也就是提取所有constructor-arg,然後進行解析,但是具體的解析卻被放置到了另一個函數

parseConstructorArgElement中,我們繼續步入:

public void parseConstructorArgElement(Element ele, BeanDefinition bd) {
    //提取index屬性
   String indexAttr = ele.getAttribute(INDEX_ATTRIBUTE);
    //提取type屬性
   String typeAttr = ele.getAttribute(TYPE_ATTRIBUTE);
    //提取name屬性
   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));
                //解析ele對應的屬性元素
               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 {
       //沒有index屬性則忽略屬性,自動尋找 
      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();
      }
   }
}
           

又是好長的代碼。。。雖然很長很複雜,但是涉及的邏輯并不複雜,首先是提取constructor-arg上必要的屬性(index、type、name)

  • 如果配置中制定了index屬性,那麼操作步驟如下
    • 解析Constructor-arg的子元素
    • 使用ConstructorArgumentValues.ValueHolder類型來封裝解析出來的元素
    • 将type、name和index屬性一并封裝在ConstructorArgumentValues.ValueHolder類型中,并添加至目前BeanDefinition的ConstructorArgumentValues的indexedArgumentValues屬性中。
  • 如果沒有指定index屬性,那麼操作步驟如下
    • 解析constructor-arg的子元素
    • 使用ConstructorArgumentValues.ValueHolder類型來封裝解析出來的元素
    • 将type、name和index屬性一并封裝在ConstructorArgumentValues.ValueHolder類型中,并添加至目前BeanDefinition的ConstructorArgumentValues的GenericArgumentValue屬性中。

可以看出,對于是否指定index屬性,spring的處理流程是不同的,關鍵在于屬性資訊被儲存的位置。

那麼了解了整個流程後,我們嘗試者進一步了解解析構造函數配置中子元素的過程,首先先看parsePropertyValue:

@Nullable
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.
    //一個屬性隻能對應一種類型:ref、value、list等
   NodeList nl = ele.getChildNodes();
   Element subElement = null;
   for (int i = 0; i < nl.getLength(); i++) {
      Node node = nl.item(i);
       //對應description或者meta不處理
      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;
         }
      }
   }
	//解析constructor-arg上的ref屬性
   boolean hasRefAttribute = ele.hasAttribute(REF_ATTRIBUTE);
    //解析constructor-arg上的value屬性
   boolean hasValueAttribute = ele.hasAttribute(VALUE_ATTRIBUTE);
   if ((hasRefAttribute && hasValueAttribute) ||
         ((hasRefAttribute || hasValueAttribute) && subElement != null)) {
       //在constructor-arg上不存在:1、同時既有ref屬性又有value屬性 2、存在ref屬性或者value屬性且又有子元素  
       //否則就會報錯
      error(elementName +
            " is only allowed to contain either 'ref' attribute OR 'value' attribute OR sub-element", ele);
   }

   if (hasRefAttribute) {
       //ref屬性的處理,使用RuntimeBeanReference封裝對應的ref名稱
      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;
   }
   else if (hasValueAttribute) {
       //value屬性的處理,使用TypedStringValue封裝
      TypedStringValue valueHolder = new TypedStringValue(ele.getAttribute(VALUE_ATTRIBUTE));
      valueHolder.setSource(extractSource(ele));
      return valueHolder;
   }
   else if (subElement != null) {
       //解析子元素
      return parsePropertySubElement(subElement, bd);
   }
   else {
       //既沒有ref也沒有value也沒有子元素,spring蒙圈了!!!!!!
      // Neither child element nor "ref" or "value" attribute found.
      error(elementName + " must specify a ref or value", ele);
      return null;
   }
}
           

從代碼上看,對構造方法中屬性元素的解析,經曆了以下幾個過程:

  • 略過description和meta
  • 提取constructor-arg上的ref和value屬性,以便于根據規則驗證正确性,其規則為在constructor-arg上不存在以下情況:1、同時既有ref屬性又有value屬性 2、存在ref屬性或者value屬性且又有子元素
  • ref屬性的處理,使用RuntimeBeanReference封裝對應的ref名稱,如
  • value屬性的處理,使用TypedStringValue封裝,如
  • 子元素的處理,如
    • <constructor-arg>
      	<map>
      		<entry key="key" value="value">
      	</map>
      </constructor-arg>
                 
      對于子元素的處理,我們步入parsePropertySubElement方法:
    @Nullable
    public Object parsePropertySubElement(Element ele, @Nullable BeanDefinition bd, @Nullable String defaultValueType) {
       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.
           //對任何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.
              //對父上下文中另一個bean的ID的引用。
              //解析parent
             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;
       }
        //解析idref元素
       else if (nodeNameEquals(ele, IDREF_ELEMENT)) {
          return parseIdRefElement(ele);
       }
        //解析value元素
       else if (nodeNameEquals(ele, VALUE_ELEMENT)) {
          return parseValueElement(ele, defaultValueType);
       }
        //解析null子元素????
       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對象中,以保留源位置。
          TypedStringValue nullHolder = new TypedStringValue(null);
          nullHolder.setSource(extractSource(ele));
          return nullHolder;
       }
        //解析array子元素
       else if (nodeNameEquals(ele, ARRAY_ELEMENT)) {
          return parseArrayElement(ele, bd);
       }
        //解析list子元素
       else if (nodeNameEquals(ele, LIST_ELEMENT)) {
          return parseListElement(ele, bd);
       }
        //解析set子元素
       else if (nodeNameEquals(ele, SET_ELEMENT)) {
          return parseSetElement(ele, bd);
       }
        //解析map子元素
       else if (nodeNameEquals(ele, MAP_ELEMENT)) {
          return parseMapElement(ele, bd);
       }
        //解析props子元素
       else if (nodeNameEquals(ele, PROPS_ELEMENT)) {
          return parsePropsElement(ele);
       }
       else {
          error("Unknown property sub-element: [" + ele.getNodeName() + "]", ele);
          return null;
       }
    }
               

可以看到,在上面的方法中實作了所有可支援的子類的分類處理,到這裡,我們已經理清了構造方法的解析流程,這裡就不繼續深入了。

解析子元素perperty

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

這個函數不難了解,無非是提取所有peoperty的子元素,然後調用parsePropertyElement處理,parsePropertyElement代碼如下:

public void parsePropertyElement(Element ele, BeanDefinition bd) {
    //擷取配置元素中的name的值
   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;
      }
      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();
   }
}
           

可以看到上面方法與構造方法注入方式不同的是将傳回值使用peopertyValue進行封裝,并記錄在了BeanDefinition中的propertyValues屬性中。

解析子元素qualifier

對于qualifier元素的擷取,我們接觸更多的是注解的形式,在使用Spring架構中進行自動注入式,Spring容器中比對的候選Bean數目必須有且僅有一個。當找不到一個比對的Bean時,Spring容器将抛出BeanCreationException異常,并指出必須至少擁有一個比對的Bean。

public void parseQualifierElements(Element beanEle, AbstractBeanDefinition bd) {
   NodeList nl = beanEle.getChildNodes();
   for (int i = 0; i < nl.getLength(); i++) {
      Node node = nl.item(i);
      if (isCandidateElement(node) && nodeNameEquals(node, QUALIFIER_ELEMENT)) {
         parseQualifierElement((Element) node, bd);
      }
   }
}
           

解析過程不多說了,和之前差不多。

AbstractBeanDefinition屬性

到這裡我就完成了XML到GenericBeanDefinition,也就是說到這裡,XMl中所有的配置都可以在GenericBeanDefinition的執行個體類中找到對應的配置。

GenericBeanDefinition隻是子類實作,而大部分的通用屬性都儲存在了AbstractBeanDefinition中,我們再次通過AbstractBeanDefinition的屬性來回顧一下我們都解析了那些對應的配置。

public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccessor
      implements BeanDefinition, Cloneable {

   //此處省略靜态變量以及final常量
	
   //bean的作用範圍,對應bean屬性scope
   @Nullable
   private String scope = SCOPE_DEFAULT;
	//是否是抽象,對應abstract
   private boolean abstractFlag = false;
	
    //是否延遲加載,對應lazy-init
   @Nullable
   private Boolean lazyInit;
	//自動注入模式,對應autowire
   private int autowireMode = AUTOWIRE_NO;
	//依賴檢查,spring3.0後棄用
   private int dependencyCheck = DEPENDENCY_CHECK_NONE;
	//用來表示一個bean的執行個體化依靠另一個bean先執行個體化,對應bean屬性depend-on
   @Nullable
   private String[] dependsOn;
	//為flase時會在容器查找自動裝配對象時,将不考慮該bean,對應autowire-candidate
   private boolean autowireCandidate = true;
	//自動裝配時出現多個bean候選者時,将作為首選者,對應primary
   private boolean primary = false;
	用于記錄qualifier,對應qualifier
   private final Map<String, AutowireCandidateQualifier> qualifiers = new LinkedHashMap<>();
	//不知道這是啥
   @Nullable
   private Supplier<?> instanceSupplier;
	//允許通路非公開的構造器和方法,程式設定
   private boolean nonPublicAccessAllowed = true;
	//是否以一種寬松的模式解析構造方法,預設為true
   private boolean lenientConstructorResolution = true;
	//對應factory-bean
   @Nullable
   private String factoryBeanName;
	//對應factory-method
   @Nullable
   private String factoryMethodName;
	//記錄構造方法注入屬性,對應constructor-arg
   @Nullable
   private ConstructorArgumentValues constructorArgumentValues;
	//普通屬性集合
   @Nullable
   private MutablePropertyValues propertyValues;
	//方法重寫的持有者,記錄lookup-method、replaced-method元素
   private MethodOverrides methodOverrides = new MethodOverrides();
	//初始化方法、對應init-method
   @Nullable
   private String initMethodName;
	//銷毀方法,對應destory-method
   @Nullable
   private String destroyMethodName;
	//是否執行init-method
   private boolean enforceInitMethod = true;
	//是否執行destroy-method
   private boolean enforceDestroyMethod = true;
	//是否是使用者定義的而不是程式本身定義的,建立AOP時為true
   private boolean synthetic = false;
	//定義這個bean的應用,APPLICATION:使用者 INFRASTRUCTURE:完全内部使用 SUPPORT:某些複雜配置的一部分
   private int role = BeanDefinition.ROLE_APPLICATION;
	//bean的描述資訊
   @Nullable
   private String description;
	//這個bean定義的資源
   @Nullable
   private Resource resource;
    
    //省略set/get以及其他方法
}
           

解析預設标簽中的自定義标簽元素

到這裡我們已經完成了預設标簽的解析與提取過程,我不由得想問自己一個問題,我看到哪了????沒錯,是這:

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

我們已經用了大量的篇幅分析了BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele)這句代碼,接下來,我們要進行bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder)這句代碼的分析,首先大緻了解下這句代碼的作用,其實我們可以從方法名上分析:如果需要的話就對beanDefinition進行裝飾,那麼這句代碼到底是什麼功能呢?其實這句代碼适用于這樣的場景,如:

<bean id="test" class="com.qust.test.TestBean">
   <mybean:user username="aaa"/>
</bean>
           

當Spring中的bean使用的是預設的标簽配置,但是其中的子元素卻使用了自定義的配置時,這句代碼便會起作用了,當然,這裡的自定義類型其實是屬性。那麼我們繼續分析下面這段代碼:

public BeanDefinitionHolder decorateBeanDefinitionIfRequired(Element ele, BeanDefinitionHolder originalDef) {
   return decorateBeanDefinitionIfRequired(ele, originalDef, null);
}
           

又是常見的重載,這裡将方法的第三個參數設定為空,那麼第三個參數是做什麼用的呢,其實這第三個參數是父類bean,當對某個嵌套配置進行分析時,這裡需要傳遞父類beanDefinition。分析源碼得知這裡傳遞的參數其實是為了使用父類的scope屬性,以備子類沒有設定scope時預設使用父類的屬性,這裡分析的是頂層配置,是以傳遞null。我們進一步跟蹤:

public BeanDefinitionHolder decorateBeanDefinitionIfRequired(
      Element ele, BeanDefinitionHolder originalDef, @Nullable BeanDefinition containingBd) {

   BeanDefinitionHolder finalDefinition = originalDef;

   // Decorate based on custom attributes first.
   NamedNodeMap attributes = ele.getAttributes();
    //周遊所有的屬性,看看是由有适用于修飾的屬性
   for (int i = 0; i < attributes.getLength(); i++) {
      Node node = attributes.item(i);
      finalDefinition = decorateIfRequired(node, finalDefinition, containingBd);
   }

   // Decorate based on custom nested elements.
   NodeList children = ele.getChildNodes();
    //便利所有的子節點,看看是否有适用于修飾的子元素
   for (int i = 0; i < children.getLength(); i++) {
      Node node = children.item(i);
      if (node.getNodeType() == Node.ELEMENT_NODE) {
         finalDefinition = decorateIfRequired(node, finalDefinition, containingBd);
      }
   }
   return finalDefinition;
}
           

上面的代碼,我們看到方法分别對元素的所有屬性以及子節點進行了decorateIfRequired方法的調用,我們繼續跟蹤代碼:

public BeanDefinitionHolder decorateIfRequired(
      Node node, BeanDefinitionHolder originalDef, @Nullable BeanDefinition containingBd) {
	//擷取自定義标簽的命名空間
   String namespaceUri = getNamespaceURI(node);
    //對與非預設标簽進行修飾
   if (namespaceUri != null && !isDefaultNamespace(namespaceUri)) {
       //根據命名空間找到對應的處理器
      NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
      if (handler != null) {
          //進行修飾
         BeanDefinitionHolder decorated =
               handler.decorate(node, originalDef, new ParserContext(this.readerContext, this, containingBd));
         if (decorated != null) {
            return decorated;
         }
      }
      else if (namespaceUri.startsWith("http://www.springframework.org/schema/")) {
         error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", node);
      }
      else {
         // A custom namespace, not to be handled by Spring - maybe "xml:...".
         // 一個自定義的名稱空間,Spring不會處理-可能是“ xml:...”
         if (logger.isDebugEnabled()) {
            logger.debug("No Spring NamespaceHandler found for XML schema namespace [" + namespaceUri + "]");
         }
      }
   }
   return originalDef;
}
           

程式走到這裡,條理其實已經非常清楚了,首先擷取屬性或者元素的命名空間,以此來判斷該元素或者屬性适用于自定義标簽的解析條件,找出自定義類型所對應的NamespaceHandler并進一步解析。這裡在後面自定義标簽的解析文章中詳細探讨,這裡先略過。

注冊解析的BeanDefinition

對于配置檔案,解析也解析完了,裝飾也裝飾完了,對于得到的beanDefinition已經滿足後續的使用要求了,唯一還剩下的工作就是注冊了,也就是processBeanDefinition方法中的BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry()),我們來到這個方法

public static void registerBeanDefinition(
      BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
      throws BeanDefinitionStoreException {

   // Register bean definition under primary name.
    //使用beanName做唯一辨別注冊
   String beanName = definitionHolder.getBeanName();
   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);
      }
   }
}
           

從上面的代碼可以看出,解析的beanDefinition都會被注冊到BeanDefinitionRegistry‘的執行個體registry中,而且與beanDefinition的注冊分成了兩部分:通過beanName的注冊以及通過别名的注冊。

通過beanName注冊BeanDefinition

對于beanDefinition的注冊,擷取很多人認為的方式是将beanDefinition直接放入map中,使用beanName作為key。确實,Spring就是這麼做的,隻不過除此之外,Spring還做了點别的事情:

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 {
          //注冊前的最後一校驗,這裡的校驗不同于之前的XML檔案校驗。
          //主要是對于AbstractBeanDefinition屬性中的methodOverrides校驗。
          //校驗methodOverrrides是否與工廠方法并存或者methodOverrrides對應的方法根本不存在。
         ((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) {
      if (!isAllowBeanDefinitionOverriding()) {
         throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);
      }
      else if (existingDefinition.getRole() < beanDefinition.getRole()) {
         // e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
          //例如是ROLE_APPLICATION,現在已被ROLE_SUPPORT或ROLE_INFRASTRUCTURE覆寫
         if (logger.isInfoEnabled()) {
            logger.info("Overriding user-defined bean definition for bean '" + beanName +
                  "' with a framework-generated bean definition: replacing [" +
                  existingDefinition + "] with [" + beanDefinition + "]");
         }
      }
      else if (!beanDefinition.equals(existingDefinition)) {
         if (logger.isDebugEnabled()) {
            logger.debug("Overriding bean definition for bean '" + beanName +
                  "' with a different definition: replacing [" + existingDefinition +
                  "] with [" + beanDefinition + "]");
         }
      }
      else {
         if (logger.isTraceEnabled()) {
            logger.trace("Overriding bean definition for bean '" + beanName +
                  "' with an equivalent definition: replacing [" + existingDefinition +
                  "] with [" + beanDefinition + "]");
         }
      }
      this.beanDefinitionMap.put(beanName, beanDefinition);
   }
   else {
      if (hasBeanCreationStarted()) {
         // Cannot modify startup-time collection elements anymore (for stable iteration)
          //無法再修改啟動時收集元素(用于穩定的疊代)
         synchronized (this.beanDefinitionMap) {
            this.beanDefinitionMap.put(beanName, beanDefinition);
            List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
            updatedDefinitions.addAll(this.beanDefinitionNames);
            updatedDefinitions.add(beanName);
            this.beanDefinitionNames = updatedDefinitions;
            removeManualSingletonName(beanName);
         }
      }
      else {
         // Still in startup registration phase
          //仍處于啟動注冊階段
         //注冊beanDefinition
         this.beanDefinitionMap.put(beanName, beanDefinition);
          //記錄beanName
         this.beanDefinitionNames.add(beanName);
         removeManualSingletonName(beanName);
      }
      this.frozenBeanDefinitionNames = null;
   }

   if (existingDefinition != null || containsSingleton(beanName)) {
       //重置所有beanName對應的緩存
      resetBeanDefinition(beanName);
   }
   else if (isConfigurationFrozen()) {
      clearByTypeCache();
   }
}
           

對于bean的注冊處理方式上,主要進行了幾個步驟:

  • 對AbstractBeanDefinition的校驗,此時的校驗是對于AbstractBeanDefinition的methodOverrides屬性的。
  • 對beanName已經注冊的情況的處理。如果設定了不允許bean的覆寫,則需要抛出異常,否則直接覆寫。
  • 加入map緩存。
  • 清除解析之前留下的對應beanName的緩存。

通過别名注冊BeanDefinition

@Override
public void registerAlias(String name, String alias) {
   Assert.hasText(name, "'name' must not be empty");
   Assert.hasText(alias, "'alias' must not be empty");
   synchronized (this.aliasMap) {
       //如果beanName和alias相同的話不記錄alias,并删除對應的alias
      if (alias.equals(name)) {
         this.aliasMap.remove(alias);
         if (logger.isDebugEnabled()) {
            logger.debug("Alias definition '" + alias + "' ignored since it points to same name");
         }
      }
      else {
         String registeredName = this.aliasMap.get(alias);
         if (registeredName != null) {
            if (registeredName.equals(name)) {
               // An existing alias - no need to re-register
               return;
            }
             //如果alias不允許被覆寫則抛出異常
            if (!allowAliasOverriding()) {
               throw new IllegalStateException("Cannot define alias '" + alias + "' for name '" +
                     name + "': It is already registered for name '" + registeredName + "'.");
            }
            if (logger.isDebugEnabled()) {
               logger.debug("Overriding alias '" + alias + "' definition for registered name '" +
                     registeredName + "' with new target name '" + name + "'");
            }
         }
          //當A->B存在時,若再次出現A->C->B時則抛出異常
         checkForAliasCircle(name, alias);
         this.aliasMap.put(alias, name);
         if (logger.isTraceEnabled()) {
            logger.trace("Alias definition '" + alias + "' registered for name '" + name + "'");
         }
      }
   }
}
           

注冊alias的步驟如下:

  • alias與beanName相同情況處理。若alias與beanName名稱相同則不需要處理并删除原有alias。
  • alias覆寫處理。若aliasName已經使用并已經指向了另一beanName則需要使用者的設定進行處理。
  • alias循環檢查。
  • 注冊alias。

通知監聽器解析及注冊完成

這裡的實作隻是為了擴充,需要開放人員對注冊BeanDefinition事件進行監聽時可以通過注冊監聽器的方式并将處理邏輯寫入監聽器中,目前在Spring中并沒有任何邏輯處理。

alias标簽的處理

alias标簽的用法就不多說了,就是個bean添加别名,用處和bean标簽中name差不多,我們直接看代碼:

protected void processAliasRegistration(Element ele) {
    //擷取beanName
   String name = ele.getAttribute(NAME_ATTRIBUTE);
    //擷取alias
   String alias = ele.getAttribute(ALIAS_ATTRIBUTE);
   boolean valid = true;
   if (!StringUtils.hasText(name)) {
      getReaderContext().error("Name must not be empty", ele);
      valid = false;
   }
   if (!StringUtils.hasText(alias)) {
      getReaderContext().error("Alias must not be empty", ele);
      valid = false;
   }
   if (valid) {
      try {
         getReaderContext().getRegistry().registerAlias(name, alias);
      }
      catch (Exception ex) {
         getReaderContext().error("Failed to register alias '" + alias +
               "' for bean with name '" + name + "'", ele, ex);
      }
      getReaderContext().fireAliasRegistered(name, alias, extractSource(ele));
   }
}
           

和之前bean中的alias解析大同小異,這裡不再贅述。

import标簽的解析

由于XML配置檔案已經很少使用了,這裡就不再過多叙述了(其實隻是因為我懶!)。

protected void importBeanDefinitionResource(Element ele) {
    //擷取resource屬性
   String location = ele.getAttribute(RESOURCE_ATTRIBUTE);
   if (!StringUtils.hasText(location)) {
      getReaderContext().error("Resource location must not be empty", ele);
      return;
   }

   // Resolve system properties: e.g. "${user.dir}"
    //解析系統屬性,格式如:"user.dir"
   location = getReaderContext().getEnvironment().resolveRequiredPlaceholders(location);

   Set<Resource> actualResources = new LinkedHashSet<>(4);
	//判定location是絕對URI還是相對URI
   // Discover whether the location is an absolute or relative URI
   boolean absoluteLocation = false;
   try {
      absoluteLocation = ResourcePatternUtils.isUrl(location) || ResourceUtils.toURI(location).isAbsolute();
   }
   catch (URISyntaxException ex) {
      // cannot convert to an URI, considering the location relative
      // unless it is the well-known Spring prefix "classpath*:"
   }

   // Absolute or relative?
    //如果是絕對URI則直接根據位址加載對應的配置檔案
   if (absoluteLocation) {
      try {
         int importCount = getReaderContext().getReader().loadBeanDefinitions(location, actualResources);
         if (logger.isTraceEnabled()) {
            logger.trace("Imported " + importCount + " bean definitions from URL location [" + location + "]");
         }
      }
      catch (BeanDefinitionStoreException ex) {
         getReaderContext().error(
               "Failed to import bean definitions from URL location [" + location + "]", ele, ex);
      }
   }
   else {
      // No URL -> considering resource location as relative to the current file.
       //如果是相對位址,則根據相對位址計算出絕對位址
      try {
         int importCount;
          //Resource有多個實作類,這裡先使用子類的方法嘗試解析
         Resource relativeResource = getReaderContext().getResource().createRelative(location);
         if (relativeResource.exists()) {
            importCount = getReaderContext().getReader().loadBeanDefinitions(relativeResource);
            actualResources.add(relativeResource);
         }
         else {
             //如果解析不成功,則使用預設的解析器ResourcePatternResolver解析
            String baseLocation = getReaderContext().getResource().getURL().toString();
            importCount = getReaderContext().getReader().loadBeanDefinitions(
                  StringUtils.applyRelativePath(baseLocation, location), actualResources);
         }
         if (logger.isTraceEnabled()) {
            logger.trace("Imported " + importCount + " bean definitions from relative location [" + location + "]");
         }
      }
      catch (IOException ex) {
         getReaderContext().error("Failed to resolve current resource location", ele, ex);
      }
      catch (BeanDefinitionStoreException ex) {
         getReaderContext().error(
               "Failed to import bean definitions from relative location [" + location + "]", ele, ex);
      }
   }
    //解析後進行監聽器激活處理
   Resource[] actResArray = actualResources.toArray(new Resource[0]);
   getReaderContext().fireImportProcessed(location, actResArray, extractSource(ele));
}
           

Spring進行解析的步驟大體如下:

  • 擷取resource屬性所表示路徑。
  • 解析路徑中的系統屬性,格式如:"${user.dir}"。
  • 判定location是絕對路徑還是相對路徑。
  • 如果是絕對路徑則遞歸調用bean的解析過程(loadBeanDefinition),進行另一次的解析。
  • 如果是相對路徑則計算出絕對路徑并進行解析。
  • 通知監聽器,解析完成。

嵌入式beans标簽的解析

沒有大多可說的,與單獨的配置檔案并沒有太大的差别,無非是遞歸調用beans的解析過程。

到這裡我們就完成了預設标簽的解析,雖然經過好幾天還停留在XML的解析階段,但是我們已經看到了勝利的曙光,下一篇文章将繼續探究自定義标簽的解析。