Spring(六):Bean标签解析(三)

回顾

前面已经看了父Bean标签对下面几种标签的解析

  • meta标签
  • lookup-method子标签
  • replaced-method子标签
  • construct-arg子标签(构造器注入)

这几种标签,其实不都怎么常用,通常我们使用最多的肯定是property标签

下面就来看看property标签是怎样被解析的

解析property标签

在配置文件中,我们一般这样使用property标签

<bean id="person" class="service.Person">
    <!-- 控制器调用setAxe方法,将容器中的axe bean作为传入的参数 -->
    <!--此处的name是决定Person类中的那个参数,ref是指bean配置文件中的bean名称-->
    <property name="axe" ref="axe"></property>
</bean>

仍然回到我们创建Bean的流程图里面(BeanDefinitionParserDelegate完成)

/**
	 * Parse the bean definition itself, without regard to name or aliases. May return
	 * {@code null} if problems occurred during the parsing of the bean definition.
	 */
	@Nullable
	public AbstractBeanDefinition parseBeanDefinitionElement(
			Element ele, String beanName, @Nullable BeanDefinition containingBean) {
		//这一步是用来在解析过程中跟踪逻辑位置的
		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 {
            //创造BeanDefinition实体(也就是bean承载)
			AbstractBeanDefinition bd = createBeanDefinition(className, parent);
			
            //硬编码解析默认bean的各种属性
			parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
			bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));

            //下面进行一些bean的属性进行加工解析
            //解析元数据
			parseMetaElements(ele, bd);
            //解析lookup-method属性
			parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
            //解析replaced-method属性
			parseReplacedMethodSubElements(ele, bd.getMethodOverrides());        
            //解析构造函数的参数
			parseConstructorArgElements(ele, bd);
            //解析property子元素
			parsePropertyElements(ele, bd);
            //解析qualified子元素
			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;
	}

parsePropertyElements方法

源码如下

	public void parsePropertyElements(Element beanEle, BeanDefinition bd) {
        //获取Bean标签的所有子标签
		NodeList nl = beanEle.getChildNodes();
		for (int i = 0; i < nl.getLength(); i++) {
			Node node = nl.item(i);
            //遍历去判断,是否遇到了property子标签
			if (isCandidateElement(node) && nodeNameEquals(node, PROPERTY_ELEMENT)) {
                //如果为property子标签
                //那就去执行parsePropertyElement方法
				parsePropertyElement((Element) node, bd);
			}
		}
	}

parsePropetyElement方法

	public void parsePropertyElement(Element ele, BeanDefinition bd) {
        //获取property标签里面的name属性
        //name属性其实就是成员属性名字
		String propertyName = ele.getAttribute(NAME_ATTRIBUTE);
        //判断name属性是否为空
		if (!StringUtils.hasLength(propertyName)) {
            //为空就报错,并直接结束
			error("Tag 'property' must have a 'name' attribute", ele);
			return;
		}
        //记录此时状态为加载property标签里面的name属性
		this.parseState.push(new PropertyEntry(propertyName));
		try {
            //判断当前的bean是否已经注册过这个property的name
            //其实就是判断当前的bean对该成员属性是否已经记录过
			if (bd.getPropertyValues().contains(propertyName)) {
                //如果已经记录过,那么就报错
                //因为重复加载了,不可能有两个成员属性的变量名是一样的
				error("Multiple 'property' definitions for property '" + propertyName + "'", ele);
				return;
			}
            //继续解析property里面的属性和子标签
			Object val = parsePropertyValue(ele, bd, propertyName);
            //解析完后,将property标签里面的所有内容,封装为PropertyValue对象
            //包括name和val其他属性
			PropertyValue pv = new PropertyValue(propertyName, val);
            //解析property标签下面的meta子标签!
			parseMetaElements(ele, pv);
			pv.setSource(extractSource(ele));
            //记录进当前bean里面,的propertyValues里面
            //代表这个Name的成员属性已经加载过了
			bd.getPropertyValues().addPropertyValue(pv);
		}
		finally {
			this.parseState.pop();
		}
	}

parsePropertyValue方法

该方法就是解析value子标签和ref子标签的!

value子标签就是代表成员属性待注入的值,而ref子标签为成员属性待注入的另外一个bean!(即成员属性是一个对象,那么可以使用ref来为这个成员属性进行注入)

源码如下(是不是感觉很熟悉,这个解析property标签的,与解析construct-arg标签的value子标签是一样的!)

	@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.
        //按照约定,construct-arg应该只有一个子标签
		NodeList nl = ele.getChildNodes();
		Element subElement = null;
        //遍历子标签
		for (int i = 0; i < nl.getLength(); i++) {
			Node node = nl.item(i);
            //筛选出不是meta和description子标签的
           	//所以construct-arg是可以有多个子标签的,但只能是meta和description
            //且倘若出现 ref, value, list, etc其中一个,后面的meta和description会导致报错
			if (node instanceof Element && !nodeNameEquals(node, DESCRIPTION_ELEMENT) &&
					!nodeNameEquals(node, META_ELEMENT)) {
				// Child element is what we're looking for.
                //筛选了之后,就代表这是我们想要的子标签
                //这也说明了一个现象,在construct-arg中.meta和description子标签是失效的!
                //下面这个判断就很有意思了
                //它是限制了constrcuct-arg里面只能有一个非meta、description的子标签
                //即外层if判断为true只能进来一次
                //第一次会为null,但第二次循环如果还能进来就不再为null了
				if (subElement != null) {
                    //设置了多个子标签,报错
					error(elementName + " must not contain more than one sub-element", ele);
				}
				else {
                    //从这次循始将要结束时,subElement就不再为null了
                    //同时这里也是记录了value子标签或其他子标签
					subElement = (Element) node;
				}
			}
		}
		//获取construct-arg的ref和value属性
		boolean hasRefAttribute = ele.hasAttribute(REF_ATTRIBUTE);
		boolean hasValueAttribute = ele.hasAttribute(VALUE_ATTRIBUTE);
        //假如两个同时拥有,或者两个属性拥有一个,但解析出的子标签不为null,证明有子标签
        //就代表出现了重复给构造参数了
		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);
			}
            //ref属性用RuntimeBeanReference对象存储
			RuntimeBeanReference ref = new RuntimeBeanReference(refName);
            //标明来源
			ref.setSource(extractSource(ele));
            //返回
			return ref;
		}
        //如果是value属性
		else if (hasValueAttribute) {
            //使用TypedStringValue进行存储
			TypedStringValue valueHolder = new TypedStringValue(ele.getAttribute(VALUE_ATTRIBUTE));
			valueHolder.setSource(extractSource(ele));
            //返回
			return valueHolder;
		}
        //如果是子标签形式
		else if (subElement != null) {
            //交由parsePropertySubElement继续处理子标签
            //因为子标签也是有多种形式的
			return parsePropertySubElement(subElement, bd);
		}
        //既没有value属性,又没有ref属性,又没有子标签
		else {
			// Neither child element nor "ref" or "value" attribute found.
			error(elementName + " must specify a ref or value", ele);
			return null;
		}
	}

这里就不再赘述了

PropertyValue是怎样被记录的?

前面不是说了,对于property标签,是会被放进去PropertyValues对象里面的(与Construct-arg标签被存放进ConstructArgumentValues对象一样)
在这里插入图片描述
BeanDefinition这个接口只有一个实现类就是AbstractBeanDefinition,也就是在AbstractBeanDefinition中,就初始化了这个存储容器了(与ConstructorArgumentValues一样),可知AbstractBeanDefinition有着记录创建Bean的细节

在这里插入图片描述
可以见到,其本质是MutablePropertyValues对象
在这里插入图片描述
在这里插入图片描述
而且其底层的容器有两个

  • PropertyValueList:一个ArrayList集合,并且默认容量为0,这个ArrayList就是用来存储PropertyValue的
  • proccessedProperties:一个String的集合,这个就是用来去重的!也就是原先判断这个property是否正在注册(通过name属性判断,),里面存储的就是name属性,本质上是一个HashSet并且初始化的容量为4

在这里插入图片描述
不过,决定这个property是否可以存放进来,是由这两个容器共同决定的
在这里插入图片描述

在这里插入图片描述

从代码上可以看到,判断这个property是否已经加载过,需要满足2个条件

  • 遍历PropertyValueList,里面没有一个propertyValue的name是与其一样的
  • 且proccessedProperties里面为空或者proccessedProperties没有这个propertyName记录

满足上面两个条件,才会返回false,代表这个property没有被加载过

下面再看看添加propertyValue进propertyValueList的逻辑

	public MutablePropertyValues addPropertyValue(PropertyValue pv) {
        //遍历存储propertyValue的propertyValueList
		for (int i = 0; i < this.propertyValueList.size(); i++) {
			PropertyValue currentPv = this.propertyValueList.get(i);
            //如果出现了重复PropertyName
			if (currentPv.getName().equals(pv.getName())) {
                //判断是否需要合并处理
				pv = mergeIfRequired(pv, currentPv);
                //合并处理后重新修改这个propertyValue
				setPropertyValueAt(pv, i);
				return this;
			}
		}
        //没有出现重复的propertyName,直接往propertyValueList里面添加
		this.propertyValueList.add(pv);
		return this;
	}

可以看到,spring的逻辑真的严谨,前面已经判断过一次了,这里又要进行判断(一般来说,应该不会走去合并处理的)

合并的逻辑

	private PropertyValue mergeIfRequired(PropertyValue newPv, PropertyValue currentPv) {
		Object value = newPv.getValue();
        //如果value实现了Mergeable接口
        //那就进行合并!
		if (value instanceof Mergeable) {
			Mergeable mergeable = (Mergeable) value;
			if (mergeable.isMergeEnabled()) {
                //value进行合并后,产生新的propertyValue并返回
				Object merged = mergeable.merge(currentPv.getValue());
				return new PropertyValue(newPv.getName(), merged);
			}
		}
        //没实现Mergeable接口,直接返回旧值
		return newPv;
	}

解析qualifier标签

终于到最后一个qualifier标签了

<bean id="animal" class="test.constructor.Animal">
	<qualifier type="test.qualifier.Person" value="student"></qualifier>
</bean>

对于qualifier我们通常都是使用注解@Qualifier的,那么这个qualifier标签有什么用呢?

要知道,在使用Spring框架中进行自动注入的时候,Spring容器中提供的候选Bean必须有而且仅仅只能有一个,当找不到匹配的Bean时,Spring容器将抛出BeanCreationException

qualifier标签就是用来定义这个bean的别名的,代表这个bean必须根据名称(ByName)才会被选为候选bean(一般时ByType),即根据bean的名称进行注入!

	/**
	 * Parse qualifier sub-elements of the given bean element.
	 */
	public void parseQualifierElements(Element beanEle, AbstractBeanDefinition bd) {
		NodeList nl = beanEle.getChildNodes();
        //同理,遍历所有子标签
		for (int i = 0; i < nl.getLength(); i++) {
			Node node = nl.item(i);
             //遇到qualifier标签就执行方法,parseQualifierElement
			if (isCandidateElement(node) && nodeNameEquals(node, QUALIFIER_ELEMENT)) {
				parseQualifierElement((Element) node, bd);
			}
		}
	}

parseQualifierElement方法

public void parseQualifierElement(Element ele, AbstractBeanDefinition bd) {
   String typeName = ele.getAttribute(TYPE_ATTRIBUTE);
    //判断type属性是否为空
    //type属性是
   if (!StringUtils.hasLength(typeName)) {
      error("Tag 'qualifier' must have a 'type' attribute", ele);
      return;
   }
    //开始跟踪
   this.parseState.push(new QualifierEntry(typeName));
   try {
       //使用AutowireCandidateQualifier去存储qualifier标签
      AutowireCandidateQualifier qualifier = new AutowireCandidateQualifier(typeName);
      qualifier.setSource(extractSource(ele));
      String value = ele.getAttribute(VALUE_ATTRIBUTE);
      if (StringUtils.hasLength(value)) {
         qualifier.setAttribute(AutowireCandidateQualifier.VALUE_KEY, value);
      }
      NodeList nl = ele.getChildNodes();
       //对Qualifier子标签进行解析!
      for (int i = 0; i < nl.getLength(); i++) {
         Node node = nl.item(i);
         if (isCandidateElement(node) && nodeNameEquals(node, QUALIFIER_ATTRIBUTE_ELEMENT)) {
            Element attributeEle = (Element) node;
             //必须要有key属性和value属性
            String attributeName = attributeEle.getAttribute(KEY_ATTRIBUTE);
            String attributeValue = attributeEle.getAttribute(VALUE_ATTRIBUTE);
            if (StringUtils.hasLength(attributeName) && StringUtils.hasLength(attributeValue)) {
               BeanMetadataAttribute attribute = new BeanMetadataAttribute(attributeName, attributeValue);
               attribute.setSource(extractSource(attributeEle));
               qualifier.addMetadataAttribute(attribute);
            }
            else {
               error("Qualifier 'attribute' tag must have a 'name' and 'value'", attributeEle);
               return;
            }
         }
      }
       //当前bean去存储qualiier
      bd.addQualifier(qualifier);
   }
   finally {
      this.parseState.pop();
   }
}

在这里插入图片描述

可以看到,存储Qualifier标签对象的是一个LinkedHashMap

至此,Bean标签解析就完成了

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值