Spring源码解析2——默认标签的解析(一)

1、bean标签的解析及注册

1.1、bean标签解析前准备

clipboard

  通过org.springframework.beans.factory.xml.BeanDefinitionParserDelegate类型的delegate对象和org.w3c.dom.Element类型的root对象,解析xml文件中的默认标签。
clipboard
clipboard
clipboard
clipboard

1.2、默认bean标签的解析

  通过解析得到BeanDefinitionHolder类型的实例,经过这个方法后,BeanDefinitionHolder已经包含了我们配置文件的各种属性了,例如:class、name、id、alias之类。
clipboard
clipboard

①、首先委托BeanDefinitionDelegate 类的parseBeanDefinitionElement() 方法进行元素析,返回BeanDefinitionHolder 类型的实例bdHolder , 经过这个方法后,bdHolder 实例已经包含我们配置文件中配置的各种属性了,例如class 、name、id 、alias 之类的属性。
②、当返回的bdHolder 不为空的情况下若存在默认标签的子节点下再有自定义属性, 还需要再次对自定义标签进行解析。
③、解析完成后, 需要对解析后的bdHolder 进行注册,同样, 注册操作委托给了BeanDefinitionReaderUtils 的registerBeanDefinition() 方法。
④、最后发出响应事件,通知相关的监昕器,这个bean 已经加载完成了。

1.2.1、通过下面解析得到BeanDefinitionHolder类型的实例

clipboard
  解析Element类型的实例ele,得到<bean>标签内的属性,最终封装到BeanDefinitionHolder.java
clipboard
clipboard
clipboard

①、提取元素中的id 以及name 属性。
②、进一步解析其他所有属性并统一封装至GenericBeanDefinition 类型的实例中。
③、如果检测到bean 没有指定beanName ,那么使用默认规则为此Bean 生成beanName 。
④、将获取到的信息封装到BeanDefinitionHolder 的实例中。

  上面图中的解析过程,蓝色框中的方法比较重要,对蓝色框的逻辑分析:

  • 蓝色框1:
    clipboard

  • 蓝色框1-findFirshMatch()
    clipboard

  此处为Spring框架定义的集合操作工具类(CollectionUtils.java),findFirstMatch(Collection<?> source,Collection<E> candidates)是很好的集合查重范本。

  • 蓝色框2:

BeanDefinitionParserDelegate.java

@Nullable
public AbstractBeanDefinition parseBeanDefinitionElement(
      Element ele, String beanName, @Nullable BeanDefinition containingBean) {
   //用来记录正在加载的beanName
   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 {
    //通过<bean class="",parent="">中的class属性和parent属性
    // 创建用于承载属性的GenericBeanDefinition
      AbstractBeanDefinition bd = createBeanDefinition(className, parent);

      //硬编码解析默认bean的各种属性
      parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
      //提取description
      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;
}
  • 蓝色框2-ParseState.java:
    clipboard

  final class ParseState 和 interface Entry 的关系类图:
clipboard

  Spring中的ParseState.java是对JDK中的LinkedList<E>进行基本封装。这个是实现封装LinkedList.java的很好范例。

  • 蓝色框2-createBeanDefinition():
    clipboard
    clipboard
    clipboard

GenericBeanDefinition.java的UML图:
clipboard

  BeanDefinition是一个接口,在Spring中存在三种实现:RootBeanDefinition.java、ChildBeanDefinition.java、GenericBeanDefinition.java。三种实现均继承了AbstractBeanDefinition.abstract。BeanDefinition.interface是<bean>标签在容器中表示形式。
  <bean>标签内拥有的class、scope、lazy-init等配置属性,在AbstractBeanDefinition.abstract中提供了相应的beanClass、scope、lazyInit属性与<bean>标签的属性一一对应。
  Spring3.0以后,用的官方建议用GenericBeanDefinition.java替代RootBeanDefinition.java、ChildBeanDefinition.java。

  • 蓝色框2-parseBeanDefinitionAttributes():

  硬编码解析:scope、singleton、abstract、lazy-init、dependency-on属性,存入GenericBeanDefinition.java中

/**
 * Apply the attributes of the given bean element to the given bean * definition.
 * @param ele bean declaration element
 * @param beanName bean name
 * @param containingBean containing bean definition
 * @return a bean definition initialized according to the bean element attributes
 */
public AbstractBeanDefinition parseBeanDefinitionAttributes(Element ele, String beanName,
      @Nullable BeanDefinition containingBean, AbstractBeanDefinition bd) {
   //解析scope属性或者singleton属性,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) {
      // Take default from containing bean in case of an inner bean definition.
      //在嵌入beanDefinition情况下,且没有单独指定scope属性则使用父类默认的属性。
      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));
   //解析dependency-on属性
   if (ele.hasAttribute(DEPENDS_ON_ATTRIBUTE)) {
      String dependsOn = ele.getAttribute(DEPENDS_ON_ATTRIBUTE);
      bd.setDependsOn(StringUtils.tokenizeToStringArray(dependsOn, MULTI_VALUE_ATTRIBUTE_DELIMITERS));
   }
   //解析autowire-candidate属性
   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)));
   }
   //解析init-method属性
   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);
   }
   //解析factory-method属性
   if (ele.hasAttribute(FACTORY_METHOD_ATTRIBUTE)) {
      bd.setFactoryMethodName(ele.getAttribute(FACTORY_METHOD_ATTRIBUTE));
   }
   //解析factory-bean属性
   if (ele.hasAttribute(FACTORY_BEAN_ATTRIBUTE)) {
      bd.setFactoryBeanName(ele.getAttribute(FACTORY_BEAN_ATTRIBUTE));
   }

   return bd;
}
1.2.2、将得到的BeanDefinitionHolder.java类型的实例,里面存储着<bean>的配置信息,注册到BeanDefinitionRegistry.interface类型的实例XmlBeanFactory.java中。Spring容器的BeanDefinitionRegistry.interface就像是Spring配置信息的数据库,主要是以map形式保存,后续直接从BeanDefinitionRegistry.interface(implement XmlBeanFactory.java)中拿数据。

clipboard
clipboard
clipboard

蓝色线条调用顺序:1->2、3
clipboard

  在Spring中有很多class通过在其他class的构造函数传入this来达到将该实例本身传递到下一个class实例中,以达到单例效果。
clipboard

红色线条调用顺序:1->2
clipboard

1.2.3、解析子元素<meta>
  • meta元素的使用
<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="testBean" name="testBean2,testBean3;" class="xxx.xxx.TestBean">
      <meta key="testStr" value="meta"></meta>
   </bean>
</beans>
package xxx.xxx;
public class TestBean {

}
package xxx.xxx;
import org.junit.Test;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;
public class AppTest {
    @Test
    public void testBean() {
      BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("spring-bean.xml"));
      System.out.println(((XmlBeanFactory) beanFactory).getBeanDefinition("testBean").getAttribute("testStr"));
   }
}

  这段代码并不会体现在TestBean.java 的属性当中,而是一个额外的声明,当需要使用里面的信息的时候可以通过BeanDefinition 的getAttribute(key)方法进行获取。
clipboard

BeanMeatadataAttribute.java implement BeanMetadataElement.interface
clipboard

1.2.4、解析子元素<lookup-method>

  子元素<lookup-method> 似乎并不是很常用,但是在某些时候它的确是非常有用的属性,通常我们称它为获取器注入。
  它是把一个方法声明为返回某种类型的bean ,但实际要返回的bean 是在配置文件里面配置的,此方法可用在设计有些可插拔的功能上,解除程序依赖。

  • 首先我们创建一个父类。
package xxx.xxx.lookupmethod;

public class Animal {
   public void showMe(){
      System.out.println("Animal...");
   }
}
  • 创建其子类并覆盖showMe 方法。
package xxx.xxx.lookupmethod;

public class Dog extends Animal {
   public void showMe(){
      System.out.println("Dog");
   }
}
  • 创建调用方法。
package xxx.xxx.lookupmethod;

public abstract class GetAnimalBean {
   public void showMe(){
      this.getBean().showMe();
   }

   public abstract Animal getBean();
}
  • 配置文件spring-lookUpMethod.xml
<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="dog" class="xxx.xxx.lookupmethod.Dog" scope="prototype"/>

   <bean id="getAnimalBean" class="xxx.xxx.lookupmethod.GetAnimalBean">
      <lookup-method name="getBean" bean="dog"/>
   </bean>
</beans>
  • 创建测试方法。
package xxx.xxx.lookupmethod;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class testMain {
   public static void main(String[] args) {
      //Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'getAnimalBean' defined in class path resource [spring-lookUpMethod.xml]: Instantiation of bean failed; nested exception is java.lang.ExceptionInInitializerError
      // at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1258)
      ApplicationContext context = new ClassPathXmlApplicationContext("spring-lookUpMethod.xml");
      GetAnimalBean bean = (GetAnimalBean) context.getBean("getAnimalBean");
      bean.showMe();
   }
}

报错如下
clipboard

  按照上面的配置和编写实体类,我们的java代码通过ApplicationContext在获取GetAnimalBean类型的实例中报错了,目前还没有找到问题的原因。

  下面是Spring解析<lookup-mehtod>的源代码,进行分析如下:
clipboard
clipboard

  上面的代码逻辑与parseMetaElements()(1.2.3、解析子元素<meta>)大同小异,最大的区别就是在if 判断中的节点名称在这里被修改为LOOKUP_METHOD_ELEMENT。还有,在数据存储上面通过使用LookupOverride 类型的实体类来进行数据承载并记录在AbstractBeanDefinition.java(GenericBeanDefinition.java extends AbstractBeanDefinition.java)中的methodOverrides 属性中。methodOverrides的实例中封装了一个Set<MethodOverride>。
clipboard
clipboard

1.2.5、解析子元素
  • 被替代的方法
package xxx.xxx.replacedmethod;

public class ChangeMethod {
   public void originalFun(){
      System.out.println("changeMe...");
   }
}
  • 替代的方法(替代者)
package xxx.xxx.replacedmethod;

import org.springframework.beans.factory.support.MethodReplacer;
import java.lang.reflect.Method;

public class ReplacerMethod implements MethodReplacer {
   @Override
   public Object reimplement(Object obj, Method method, Object[] args) throws Throwable {
      System.out.println("我替换了原有方法...");
      return null;
   }
}
  • 配置替代者生效spring-replacedMethod.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="replacer" class="xxx.xxx.replacedmethod.ReplacerMethod"/>

   <bean id="changeMethod" class="xxx.xxx.replacedmethod.ChangeMethod">
      <replaced-method name="originalFun" replacer="replacer"/>
   </bean>
</beans>
  • main方法测试
package xxx.xxx.replacedmethod;
import xxx.xxx.lookupmethod.GetAnimalBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class testMain {
   public static void main(String[] args) {
      ApplicationContext context = new ClassPathXmlApplicationContext("spring-replacedMethod.xml");
      ChangeMethod bean = (ChangeMethod) context.getBean("changeMethod");
      bean.originalFun();
   }
}

报错如下(和1.2.4<lookup-method>子元素报错的地方一致,怀疑自己环境有问题)
clipboard

下面是Spring解析<replaced-method>的源代码,进行分析如下:
clipboard
clipboard

  我们可以看到无论是<look-up>标签解析 还是<replaced-method> 标签解析 ,都是构造了一个MethodOverride.abstract类型的实例,并最终记录在了AbstractBeanDefinition 中的methodOverrides 属性中。而这个属性如何使用,我们会在后续的章节进行详细的介绍。

1.2.6、解析子元素<constructor-arg>
  • 首先创建一个java的Bean
package xxx.xxx.constructorArg;

public class ConstructorBean {
   private String username;
   private String password;

   public ConstructorBean(String username, String password) {
      this.username = username;
      this.password = password;
   }

   @Override
   public String toString() {
      return "ConstructorBean{" +
            "username='" + username + '\'' +
            ", password='" + password + '\'' +
            '}';
   }
}
  • 用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="constructorBean" class="xxx.xxx.constructorArg.ConstructorBean">
      <constructor-arg index="0">
         <value>chenglong</value>
      </constructor-arg>
      <constructor-arg index="1">
         <value>******</value>
      </constructor-arg>
   </bean>
</beans>
  • 用ClassPathXmlApplicaitonContext实例函数加载
package xxx.xxx.constructorArg;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class testMain {
   public static void main(String[] args) {
      ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-constructorBean.xml");
      ConstructorBean bean = (ConstructorBean) context.getBean("constructorBean");
      System.out.println(bean);
   }
}

下面是Spring解析<constructor-arg>标签的源代码,进行分析如下:
clipboard

/**
 * Parse a constructor-arg element.
 */
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));
               //解析<constructor-arg>标签下对应的子标签
               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 {
         //没有index属性则忽略此属性,自动寻找构造函数中对应的参数
         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();
      }
   }
}

逻辑梳理:配置中指定了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 的genericArgumentValues 属性中。

  解析<constructor-arg>子元素的方法:

/**
 * Get the value of a property element. May be a list etc.
 * Also used for constructor arguments, "propertyName" being null in this case.
 */
@Nullable
public Object parsePropertyValue(Element ele, BeanDefinition bd, @Nullable String propertyName) {
   String elementName = (propertyName != null ?
         "<property> element for property '" + propertyName + "'" :
         "<constructor-arg> element");
   //一个<constructor-arg>标签下只能包含一种子元素:<ref>,<value>,<list>等
   // Should only have one child element: ref, value, list, etc.
   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;
   }
}

从代码上来看,对构造函数中属性元素的解析,经历了以下几个过程:
1、 略过description 或者meta 。
2、 提取constructor-arg 上的ref 和 value 属性,以便于根据规则验证正确性,其规则为在constructor-arg 上不存在以下情况:
①、同时既有ref 属性又有value 属性。
②、存在ref 属性或者value 属性且又有子元素。
3、ref 属性的处理。使用RuntimeBeanReference 封装对应的ref 名称,如:<constructor-arg ref="a">
4、value属性的处理。使用TypedStringValue 封装,如:<constructor-arg value=” a ” <constructor-arg value=” a ” >>
5. 子元素的处理,如下代码:

<constructor-arg>
    <map>
        <entry key=” key ” value=” value” />
    </map>
</constructor- arg>

而对于子元素的处理, 例如这里提到的在构造函数中又嵌入了子元素<value>、<map>、<set>、<list>、<array>等等...

/**
 * Parse a value, ref or collection sub-element of a property or
 * constructor-arg element.
 * @param ele subelement of property element; we don't know which yet
 * @param bd the current bean definition (if any)
 */
@Nullable
public Object parsePropertySubElement(Element ele, @Nullable BeanDefinition bd) {
   return parsePropertySubElement(ele, bd, null);
}

/**
 * Parse a value, ref or collection sub-element of a property or
 * constructor-arg element.
 * @param ele subelement of property element; we don't know which yet
 * @param bd the current bean definition (if any)
 * @param defaultValueType the default type (class name) for any
 * {@code <value>} tag that might be created
 */
@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.
      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.
         //解析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 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;
   }
}
1.2.7、解析子元素<property>
/**
 * Parse property sub-elements of the given bean element.
 */
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);
      }
   }
}

clipboard

解析<property>标签与解析<constructor-arg>标签的函数相同,通过入参来进行区别。

/**
 * Get the value of a property element. May be a list etc.
 * Also used for constructor arguments, "propertyName" being null in this case.
 */
//通过是否传入String propertyName区别要解析的Element。
//1、propertyName=null,要解析的Element为<constructor-arg>;2、propertyName="xxx",要解析的Element为<property name="xxx">
@Nullable
public Object parsePropertyValue(Element ele, BeanDefinition bd, @Nullable String propertyName) {
   String elementName = (propertyName != null ?
         "<property> element for property '" + propertyName + "'" :
         "<constructor-arg> element");
   //一个<constructor-arg>标签下只能包含一种子元素:<ref>,<value>,<list>等
   // Should only have one child element: ref, value, list, etc.
   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;
   }
}

  可以看到上面函数与构造函数注入方式不同的是将返回值使用PropertyValue.java 进行封装,并记录在了BeanDefinition.java 中的propertyValues 属性中。
clipboard

posted @ 2025-12-23 23:53  Carey_ccl  阅读(5)  评论(0)    收藏  举报