Spring(五):bean标签解析(二)

前面提到了,Spring对于bean标签的解析

  • 单独使用Element解析了id和name属性
    • 没有id会有默认策略去生产id,具体回看上一篇文章
  • 解析其他标签

解析其他标签

/**
	 * 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;
	}

整体步骤如下

  • 开启追踪过程
  • 创建beanDefinition实体去承载Bean
  • 硬编码去解析默认bean的各种属性
  • 对一些特殊属性进行二次加工
    • 元数据
    • property子元素
    • qualifier子元素
    • 构造函数的参数
    • lookup-method属性
    • replaced-method属性
  • 关闭追踪

下面对每一步进行说明一下

开启追踪、并获取class和parent属性

在这里插入图片描述
这一步很简单。

  • 将当前正在解析的Bean放在ParseState(底层是一个队列),通过队列去追踪当前解析的Bean。

  • 解析Element去获取parent和class属性

创建用于属性承载的BeanDefinition

在这里插入图片描述
可以看到第一步的代码就是去创建BeanDefinition(BeanDefinitionParserDelegate中的方法),前面提到过,BeanDefinition其实相当于就是Bean,不信可以看一下官方文档

在这里插入图片描述

下面来看一下这个BeanDefinition
在这里插入图片描述

BeanDefinition是一个接口,有4个实现类,其中三个继承了AbstractBeanDefinition

BeanDefinition对应的就是配置文件元素标签在容器中的内部表示形式,说白了就是一个BeanDefinition就是IOC容器里的bean

bean标签里面的class、scope、lazy-init等配置属性,都会与BeanDefinition提供的方法一一对应

在这里插入图片描述

AbstractBeanDefinition

下面来看下这个AbstractBeanDefinition抽象类

在这里插入图片描述

可以看到,继承该抽象类的子类有三个

  • RootBeanDefinition:对应一般的bean元素标签,通俗来说,就是没有父bean的bean
  • ChildBeanDefinition:对应bean元素标签里面的bean元素标签,也就是bean的子标签
  • GenericBeanDefinition:2.5版本才加入的,一站式服务类

整体的流程就是

Spring通过BeanDefinition将配置文件里面的bean转换为容器的内部表示(具体就是RootBeanDefinition、ChildBeanDefinition、GenericBeanDefinition,这三个BeanDefinition),然后将这些BeanDefinition注册到BeanDefinitionRegistry。BeanDefinitionRegistry相当于Spring存储配置信息的内存数据库,使用map的形式去保存的,后面的操作就可以直接从BeanDefinitionRegistry中去读取配置信息

createBeanDefinition方法
	/**
	 * Create a bean definition for the given class name and parent name.
	 * @param className the name of the bean class
	 * @param parentName the name of the bean's parent bean
	 * @return the newly created bean definition
	 * @throws ClassNotFoundException if bean class resolution was attempted but failed
	 */
	protected AbstractBeanDefinition createBeanDefinition(@Nullable String className, @Nullable String parentName)
			throws ClassNotFoundException {
	//可以看到,这里创建bean的工作交由了BeanDefinitionReaderUtils去做了
		return BeanDefinitionReaderUtils.createBeanDefinition(
				parentName, className, this.readerContext.getBeanClassLoader());
	}

BeanDefinitionReaderUtils的源码如下

	public static AbstractBeanDefinition createBeanDefinition(
			@Nullable String parentName, @Nullable String className, @Nullable ClassLoader classLoader) throws ClassNotFoundException {
		//创建GenericBeanDefiniton
        //使用GenericBeanDefinition
		GenericBeanDefinition bd = new GenericBeanDefinition();
        //注入parent(parent可能为空)
		bd.setParentName(parentName);
        //如果className不为空
		if (className != null) {
            //如果className不为空,classLoader也不为空
			if (classLoader != null) {
                //通过反射去获取bean对应java对象的class对象
                //并且注入进bean中
				bd.setBeanClass(ClassUtils.forName(className, classLoader));
			}
            //如果className不为空,但没有给classLoader
			else {
                //因为没有classLoader
                //所以只将className保存进bean中
				bd.setBeanClassName(className);
			}
		}
        //返回GenericBeanDefinition
		return bd;
	}

从代码上可以看到

  • 创建Bean的实际工作是由BeanDefinitionReaderUtils去做的
  • 一开始创建的BeanDefinition类型为GenericBeanDefinition
  • 注入parent与class属性
    • class属性代表的是Java类型,如果有给上classLoader,那就给BeanDefinition注入class对象
    • 如果没有给上classLoader,那就注入class属性的值
    • 这两者都是可以为空的!

解析各种属性

创建了BeanDefinition之后,就有了实例去承载bean标签的配置信息了,可以对bean的各种配置信息进行解析了

parseBeanDefinitionAttributes

首先是经过parseBeanDefinitionAttributes方法解析(Element就是bean在xml的配置信息)

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

        //判断是否有singleton属性
        //如果有,就直接error
		if (ele.hasAttribute(SINGLETON_ATTRIBUTE)) {
            //1.x版本的singleton已经被scope取代了!
            //之前该标签是用来控制bean是否为单例的!
			error("Old 1.x 'singleton' attribute in use - upgrade to 'scope' declaration", ele);
		}
        //下面两个else if的判断
        //其实是判断有没有给bean设置scope属性
        //scope其实就是bean的作用域(官方文档说有6种,并且可以自定义)
		else if (ele.hasAttribute(SCOPE_ATTRIBUTE)) {
            //如果设置了scope属性,那就采用配置文件的
			bd.setScope(ele.getAttribute(SCOPE_ATTRIBUTE));
		}
        //如果配置文件没有配置,那就再判断containingBean是否为空
        //针对子bean标签
		else if (containingBean != null) {
            //这个设置是针对子bean标签的,因为只有bean子标签才会有containning bean
            //也就是父标签,所以默认的子bean的scope是父bean一样的!
            //这里避免是内部bean,所以使用containning bean的scope来作为默认的参考
			// Take default from containing bean in case of an inner bean definition.
			bd.setScope(containingBean.getScope());
		}
		//判断有没有abstract属性
		if (ele.hasAttribute(ABSTRACT_ATTRIBUTE)) {
            //有abstract属性就进行,并且还规定abstract属性必须只能为true或者false
            //否则会失效
            //通过用true字符串去进行equals比较
			bd.setAbstract(TRUE_VALUE.equals(ele.getAttribute(ABSTRACT_ATTRIBUTE)));
		}
		//解析lazyInit属性
		String lazyInit = ele.getAttribute(LAZY_INIT_ATTRIBUTE);
        //判断lazyInit属性是不是为default
        //为空,或者值为default,返回true
		if (isDefaultValue(lazyInit)) {
            //为空,或者lazyInit属性为default,那就设置默认值
            //默认值由DocumentDefaultsDefinition决定
			lazyInit = this.defaults.getLazyInit();
		}
        //注入lazyInit属性进bean
		bd.setLazyInit(TRUE_VALUE.equals(lazyInit));
		//获取autowire属性
		String autowire = ele.getAttribute(AUTOWIRE_ATTRIBUTE);
        //注入autowire属性,总共有4种,其中一种弃用
        //1.byName,2.byType,3.construct,4.autodetect
		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));
		}
		//解析autowire-candidate属性
		String autowireCandidate = ele.getAttribute(AUTOWIRE_CANDIDATE_ATTRIBUTE);
        //如果没有设置,或者为default
		if (isDefaultValue(autowireCandidate)) {
            //那就使用默认值
			String candidatePattern = this.defaults.getAutowireCandidates();
            //autowire-candidate属性可以设置多个,使用逗号间隔
            //只有默认值,才会有多个的情况!
			if (candidatePattern != null) {
                //所以如果有设计的话,需要解析并且使用字符串数组来接收
				String[] patterns = StringUtils.commaDelimitedListToStringArray(candidatePattern);
				bd.setAutowireCandidate(PatternMatchUtils.simpleMatch(patterns, beanName));
			}
		}
		else {
            //如果不是默认值,只可能是true或者false
            //同理使用true字符串去进行equals比较
			bd.setAutowireCandidate(TRUE_VALUE.equals(autowireCandidate));
		}
		//解析primary属性
		if (ele.hasAttribute(PRIMARY_ATTRIBUTE)) {
            //同理为true或者为false
            //不为空就进行注入
			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);
		}
        //如果init-method属性没有设置(创建的时候执行的方法)
        //那就去判断有没有默认值
		else if (this.defaults.getInitMethod() != null) {
            //没有设置且有默认值,init-method属性就设为默认值
			bd.setInitMethodName(this.defaults.getInitMethod());
            //并且init-method的方法设置为不强制执行
            //因为是默认的,所以不强制执行
			bd.setEnforceInitMethod(false);
		}
		//解析destory-method属性
        //bean销毁时执行的方法
        //与method-init属性一样,有的注入,没有就判断默认的有没有
        //没有设置且有默认的,是不会强制执行
		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. 创建BeanDefinition,为GenericBeanDefinition
  2. 对bean的所有属性进行解析

至此,该bean标签基本上已经所有属性已经解析完成了,剩下的就是里面的子标签了

scope作用域与init-method、destory-method生命周期

官方文档截图
在这里插入图片描述
在这里插入图片描述

parseMetaElements

parseBeanDefinitionAttributes已经帮我们将bean的属性都解析完了,接下来就是子标签的解析了

子标签的解析,首先是对元数据meta的解析

可能很少人用过meta子标签,因为meta标签的作用其实就是做一个额外的声明信息,声明了之后,该bean对应的BeanDefinition可以通过getAttribute方法去获取里面的信息

在这里插入图片描述
下面是源码
在这里插入图片描述

	public void parseMetaElements(Element ele, BeanMetadataAttributeAccessor attributeAccessor) {
        //meta是子标签
        //所以,先要获取当前标签的所有子标签
		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;
                //取meta子标签的key和value属性
				String key = metaElement.getAttribute(KEY_ATTRIBUTE);
				String value = metaElement.getAttribute(VALUE_ATTRIBUTE);
                //meta子标签对应的对象为BeanMetadataAttribute
				BeanMetadataAttribute attribute = new BeanMetadataAttribute(key, value);
				attribute.setSource(extractSource(metaElement));
                //存进BeanDefinition中(因为放进来的参数为BeanDefinition)
                //底层为一个LinkedHashMap
                //底层是交由AttributeAccessorSupport去实现的
				attributeAccessor.addMetadataAttribute(attribute);
			}
		}
	}

在这里插入图片描述

	@Override
	public void setAttribute(String name, @Nullable Object value) {
		Assert.notNull(name, "Name must not be null");
        //如果value为null
        //代表删除动作
        //不为null,才是插入,插入进底层的LinkedHashMap中
		if (value != null) {
			this.attributes.put(name, value);
		}
		else {
			removeAttribute(name);
		}
	}

在这里插入图片描述

parseLookupOverrideSubElements

解析来解析的是lookup-method子标签

这个子标签跟meta一样,我们都很少使用,但在某些场景,这个标签是非常有用的,这个标签通常称为获取器注入

它的作用是,可以去直接获取其他bean作为方法的返回值,即父bean把一个方法声明为返回某一个类型的bean,我们可以通过lookup-method的子标签,从spring容器中去取出这个bean,然后给父bean的方法进行返回,总的来说,就是替换了返回bean

	public void parseLookupOverrideSubElements(Element beanEle, MethodOverrides overrides) {
        //原理与meta解析几乎一致
        //获取子标签
		NodeList nl = beanEle.getChildNodes();
        //遍历子标签
		for (int i = 0; i < nl.getLength(); i++) {
			Node node = nl.item(i);
            //判断是否是lookup-method标签
			if (isCandidateElement(node) && nodeNameEquals(node, LOOKUP_METHOD_ELEMENT)) {
				Element ele = (Element) node;
                //获取lookup-method标签的name和bean属性
                //bean对应的是容器中的另外一个bean的id属性
                //name属性对应的是父bean的方法名!
				String methodName = ele.getAttribute(NAME_ATTRIBUTE);
				String beanRef = ele.getAttribute(BEAN_ELEMENT);
                //lookup-method标签对应的java对象为LookUpOverride
				LookupOverride override = new LookupOverride(methodName, beanRef);
				override.setSource(extractSource(ele));
                //同理放入BeanDefinition中
                //这里BeanDefinition存储的底层是一个CopyOnWriteArraySet
				overrides.addOverride(override);
			}
		}
	}

在这里插入图片描述

parseReplacedMethodSubElements

接下来是解析replaced-method子标签

这个标签也很少有,也还是说一下它的用途,replaced-method标签可以取替换返回实体bean,还可以动态地更改原有方法的逻辑,总的来说就是可以将父bean的方法整体给替换掉,也就是执行的方法已经是另外一个bean的方法了,但这个方法并不是可以随便写的,必须另外一个bean实现MethodReplacer接口,重写的那个方法才可以进行替换

下面是解析的源码

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;
                //获取name属性,代表要被替换的方法名
				String name = replacedMethodEle.getAttribute(NAME_ATTRIBUTE);
                //获取replace属性,代表要替换成哪个bean的id
				String callback = replacedMethodEle.getAttribute(REPLACER_ATTRIBUTE);
                //replace-method标签对应的Java实体为replaceOverride
				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));
                //同样存放进BeanDefinitions,注意这里的存放地方与lookup-method是同一个Set
                //因为都是MethodOverrides
				overrides.addOverride(replaceOverride);
			}
		}
	}
parseConstryctorArgElements

下面就是到我们常用的子标签了,解析construct-arg标签

该标签就是可以使用对应的构造函数去创建bean

	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)) {
				//判断如果是construct-arg子标签,进行解析
                parseConstructorArgElement((Element) node, bd);
			}
		}
	}
parseConstructorArgElement

这个方法是可以解析一个construct-arg标签的逻辑(这代码量一看就十分复杂)

	public void parseConstructorArgElement(Element ele, BeanDefinition bd) {
        //解析construct-arg标签里面的index、type和name属性
		String indexAttr = ele.getAttribute(INDEX_ATTRIBUTE);
		String typeAttr = ele.getAttribute(TYPE_ATTRIBUTE);
		String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
        //判断index属性是否为空
		if (StringUtils.hasLength(indexAttr)) {
            //将索引值转为Integer类型,并且判断不能小于0
			try {
				int index = Integer.parseInt(indexAttr);
				if (index < 0) {
                    //索引值小于0 报错
					error("'index' cannot be lower than 0", ele);
				}
				else {
                    //索引值正确
					try {
                        //开启跟踪
						this.parseState.push(new ConstructorArgumentEntry(index));
						//获取construct-arg的value子标签里面的值
                        Object value = parsePropertyValue(ele, bd, null);
                        //使用ValueHolder去封装value
                        //因为后续还需要保存该value对应构造函数的哪个变量名、哪种类型
						ConstructorArgumentValues.ValueHolder valueHolder = new ConstructorArgumentValues.ValueHolder(value);
                        //如果有给type和name属性(construct-arg标签)
                        //就与value一起保存进valueHolder里面
						if (StringUtils.hasLength(typeAttr)) {
							valueHolder.setType(typeAttr);
						}
						if (StringUtils.hasLength(nameAttr)) {
							valueHolder.setName(nameAttr);
						}
                        //valueHolder标明来源于哪个标签
						valueHolder.setSource(extractSource(ele));
                        //这里是判断BeanDefinition里面是否已经存在这个索引了
                        //如果已经存在这个索引,就代表出现了相同的索引
                        //一个构造方法怎么可能会出现相同索引的参数value
						if (bd.getConstructorArgumentValues().hasIndexedArgumentValue(index)) {
                            //报错
							error("Ambiguous constructor-arg entries for index " + index, ele);
						}
                        //如果没有出现过
						else {
                            //那就存进去
                            //从这里可以看到,存储构造函数的底层是一个LinkedHashMap
                            //ConstructorArgumentVlues就是存储BeanDefinition的构造参数的
                            //并且通过Map,来让索引值和value可以映射
							bd.getConstructorArgumentValues().addIndexedArgumentValue(index, valueHolder);
						}
					}
					finally {
                        //接触监控
						this.parseState.pop();
					}
				}
			}
			catch (NumberFormatException ex) {
                //索引值类型不正确,报错
				error("Attribute 'index' of tag 'constructor-arg' must be an integer", ele);
			}
		}
        //如果index属性为空
        //那就按顺序
		else {
			try {
                //与前面是一样的过程
				this.parseState.push(new ConstructorArgumentEntry());
				Object value = parsePropertyValue(ele, bd, null);
				ConstructorArgumentValues.ValueHolder valueHolder = new ConstructorArgumentValues.ValueHolder(value);
				if (StringUtils.hasLength(typeAttr)) {
					valueHolder.setType(typeAttr);
				}
				if (StringUtils.hasLength(nameAttr)) {
					valueHolder.setName(nameAttr);
				}
				valueHolder.setSource(extractSource(ele));
                //注意这里,使用ConstructorArgumentValues去存放构造参数值
                //不再使用LinkedHashMap了,而是一个ArrayList
                //因为都没给索引,不需要进行Key,value映射,直接用ArrayList的底层数组索引来代表即可
                //可以按顺序进行存放
				bd.getConstructorArgumentValues().addGenericArgumentValue(valueHolder);
			}
			finally {
                //解除监控
				this.parseState.pop();
			}
		}
	}

在这里插入图片描述
整个流程大体是

  • 解析construct-arg的name、type和index属性
    • 如果配置了Index属性
      • 使用ValueHolder去封装解析出来的元素
      • 并且将type、name和index一起封装进valueHolder
      • 判断ConstructorArgumentValues的indexArgumentValues中是否已经有这个该索引位置
      • 最后将valueHolder存放进ConstructorArgumentValues的indexArgumentValues中,即LinkedHashMap里面,使用(index,valueHolder)的键值对方式
    • 如果没有配置Index属性
      • 同样使用ValueHolder去封装解析出来的元素
      • 并且将Type、name和index一起封装进valueHolder
      • 最后将valueHolder存放进ConstructArgumentValues的genericArgumentValues中,即ArrayList里面,这样设计是想以默认升序顺序来顶替缺省的Index(ArrayList的索引可以充当value的索引,并且只能按顺序去存放,也是因为按顺序去存放value,所以不需要考虑这个索引位置已经存在value)
parsePropertyValue

在这里插入图片描述
这个方法就是处理当前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;
		}
	}

从代码上可以看出几个重要点

  • 不可以同时存在ref、value和子标签
  • 子标签只能出现一次
  • meta、description标签会默认不参与循环计算,既标签失效
  • 对于ref、value、子标签分别写了一套处理,使用不同对象去承载并返回
parsePropertySubElement

这个方法是处理子标签的

下面是源码

在这里插入图片描述

	@Nullable
	public Object parsePropertySubElement(Element ele, @Nullable BeanDefinition bd, @Nullable String defaultValueType) {
        //判断是不是默认的命名空间
		if (!isDefaultNamespace(ele)) {
            //不是的话,交由另外一组实现(注释方式,这里不做讲解了)
			return parseNestedCustomElement(ele, bd);
		}
        //判断是不是bean标签
		else if (nodeNameEquals(ele, BEAN_ELEMENT)) {
            //bean标签的处理
			BeanDefinitionHolder nestedBd = parseBeanDefinitionElement(ele, bd);
			if (nestedBd != null) {
				nestedBd = decorateBeanDefinitionIfRequired(ele, nestedBd, bd);
			}
			return nestedBd;
		}
        //判断是不是ref标签
        //做ref的处理
		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;
		}
        //判断是不是idref标签
        //做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);
		}
        //其他标签就是类型出错了,报错并返回Null
		else {
			error("Unknown property sub-element: [" + ele.getNodeName() + "]", ele);
			return null;
		}
	}

可以看到,对于子标签的处理,其实在BeanDefinitionParserDelegate里面几乎给所有标签都做了一套对应处理,通过判断子标签类型然后调用对应处理方法即可

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值