栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

《Spring》第九篇 XML配置文件的解析过程

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

《Spring》第九篇 XML配置文件的解析过程

在学习Spring DI依赖注入时, 虽然现在大多使用的是通过注解的形式来实现, 确实,使用注解,简单高效,灵活, 基本上完全替代了使用XML配置文件的形式。但是,个人认为一个技术的兴起继而替代另一个技术,那么除了新技术的优势之外,老技术的缺点也是有必要了解下的。
很好奇, 系统是如何读取到xml文件中的内容的,以及各种标签, 接下来借助源码,来学习下这个解析过程
注: Spring源码的版本是5.0

1. 当启动ClassPathXmlApplicationContext容器时, 为指定的BeanFactory创建一个XmlBeanDefinitionReader阅读器。

源码: AbstractXmlApplicationContext.loadBeanDefinitions()

   /**
	 * Loads the bean definitions via an XmlBeanDefinitionReader.
	 *
	 * @see org.springframework.beans.factory.xml.XmlBeanDefinitionReader
	 * @see #initBeanDefinitionReader
	 * @see #loadBeanDefinitions
	 */
	@Override
	protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
		// Create a new XmlBeanDefinitionReader for the given BeanFactory.
		// 创建一个XmlBeanDefinitionReader阅读器
		XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

		// Configure the bean definition reader with this context's
		// resource loading environment.
		beanDefinitionReader.setEnvironment(this.getEnvironment());
		beanDefinitionReader.setResourceLoader(this);  // 设置资源加载器
		beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

		// Allow a subclass to provide custom initialization of the reader,
		// then proceed with actually loading the bean definitions.
		initBeanDefinitionReader(beanDefinitionReader);
		loadBeanDefinitions(beanDefinitionReader);  // ★ 核心: 解析资源,生成BeanDefinition
	}
2. 获取一个资源加载器ResourceLoader, 来加载文件资源并且将其封装为Resourced。

源码: AbstractBeanDefinitionReader.loadBeanDefinitions()

   /**
	 * Load bean definitions from the specified resource location.
	 * 

The location can also be a location pattern, provided that the * ResourceLoader of this bean definition reader is a ResourcePatternResolver. * * @param location the resource location, to be loaded with the ResourceLoader * (or ResourcePatternResolver) of this bean definition reader * @param actualResources a Set to be filled with the actual Resource objects * that have been resolved during the loading process. May be {@code null} * to indicate that the caller is not interested in those Resource objects. * @return the number of bean definitions found * @throws BeanDefinitionStoreException in case of loading or parsing errors * @see #getResourceLoader() * @see #loadBeanDefinitions(org.springframework.core.io.Resource) * @see #loadBeanDefinitions(org.springframework.core.io.Resource[]) */ public int loadBeanDefinitions(String location, @Nullable Set actualResources) throws BeanDefinitionStoreException { ResourceLoader resourceLoader = getResourceLoader(); if (resourceLoader == null) { throw new BeanDefinitionStoreException( "Cannot import bean definitions from location [" + location + "]: no ResourceLoader available"); } if (resourceLoader instanceof ResourcePatternResolver) { // Resource pattern matching available. try { Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location); int loadCount = loadBeanDefinitions(resources); if (actualResources != null) { for (Resource resource : resources) { actualResources.add(resource); } } if (logger.isDebugEnabled()) { logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]"); } return loadCount; } catch (IOException ex) { throw new BeanDefinitionStoreException( "Could not resolve bean definition resource pattern [" + location + "]", ex); } } else { // Can only load single resources by absolute URL. // 将"spring-config.xml"加载成一个资源Resource,然后解析资源文件中的数据信息 Resource resource = resourceLoader.getResource(location); int loadCount = loadBeanDefinitions(resource); if (actualResources != null) { actualResources.add(resource); } if (logger.isDebugEnabled()) { logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]"); } return loadCount; } }

3. 再调用XmlBeanDefinitionReader的loadBeanDefinitions(Resource resource)方法,将资源Resourced进一步封装为EncodedResource对象,查看源码可以发现里面增加了对字符集和编码的封装,从命名上来看也可以体现出来,将资源封装完成后,就调用重载的同名方法loadBeanDefinitions()来加载资源。

源码: XmlBeanDefinitionReader.loadBeanDefinitions(EncodedResource encodedResource)

   /**
	 * Load bean definitions from the specified XML file.
	 *
	 * @param encodedResource the resource descriptor for the XML file,
	 *                        allowing to specify an encoding to use for parsing the file
	 * @return the number of bean definitions found
	 * @throws BeanDefinitionStoreException in case of loading or parsing errors
	 */
	public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
		Assert.notNull(encodedResource, "EncodedResource must not be null");
		if (logger.isInfoEnabled()) {
			logger.info("Loading XML bean definitions from " + encodedResource);
		}

		Set currentResources = this.resourcesCurrentlyBeingLoaded.get();
		if (currentResources == null) {
			currentResources = new HashSet<>(4);
			this.resourcesCurrentlyBeingLoaded.set(currentResources);
		}
		if (!currentResources.add(encodedResource)) {
			throw new BeanDefinitionStoreException(
					"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
		}
		try {
			// 获取资源文件流
			InputStream inputStream = encodedResource.getResource().getInputStream();
			try {
				InputSource inputSource = new InputSource(inputStream);
				if (encodedResource.getEncoding() != null) {
					inputSource.setEncoding(encodedResource.getEncoding());
				}
				// 加载xml文件
				return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
			} finally {
				inputStream.close();
			}
		} catch (IOException ex) {
			throw new BeanDefinitionStoreException(
					"IOException parsing XML document from " + encodedResource.getResource(), ex);
		} finally {
			currentResources.remove(encodedResource);
			if (currentResources.isEmpty()) {
				this.resourcesCurrentlyBeingLoaded.remove();
			}
		}
	}
4. 将xml文件做为一个资源文件,将其转化为SAX输入源InputSource的形式进行加载, 再使用配置的documentLoader实际加载指定的资源文件, 然后生成一个DOM document文档

源码: XmlBeanDefinitionReader.doLoadBeanDefinitions()

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException {
		try {
			// 使用配置的documentLoader实际加载指定的资源文件, 然后生成一个DOM document文档
			document doc = doLoaddocument(inputSource, resource);
			// 从DOM 文档中解析出BeanDefinition
			return registerBeanDefinitions(doc, resource);
		} catch (BeanDefinitionStoreException ex) {
			throw ex;
		} 
		........
	}
5. 获取一个BeanDefinitiondocumentReader阅读器,去解析DOM document文档, 尤其是读取标签元素然后封装成Element,通过BeanDefinitionParserDelegate解析器来解析Element中的所有根级别的子标签, 尤其是,,

源码: DefaultBeanDefinitiondocumentReader.parseBeanDefinitions()

   /**
	 * Parse the elements at the root level in the document:
	 * "import", "alias", "bean".
	 *
	 * @param root the DOM root element of the document
	 */
	protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
		// root 是从spring-config.xml中解析出来的标签信息
		if (delegate.isDefaultNamespace(root)) {
			// nl 是标签中子标签, 常见的子标签有,,
			NodeList nl = root.getChildNodes();
			for (int i = 0; i < nl.getLength(); i++) {
				Node node = nl.item(i);  // 拿到每一个子标签,去单独解析里面的元素
				if (node instanceof Element) {
					Element ele = (Element) node;
					if (delegate.isDefaultNamespace(ele)) {
						parseDefaultElement(ele, delegate);
					} else {
						delegate.parseCustomElement(ele);
					}
				}
			}
		} else {
			delegate.parseCustomElement(root);
		}
	}
6. 获取到标签中所有根子标签后,然后分别解析每个根子标签中的元素信息

标签: 主要是在一个xml配置文件中导入另一个xml文件,核心元素 resource标签: 主要是给Bean配置别名,核心元素name为Bean的名称,alias为配置的别名标签: 定义Bean, 一个标签对应一个实例Bean
此处重点描述解析标签中子元素, 见源码BeanDefinitionParserDelegate.parseBeanDefinitionElement()

 /**
	 * 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));
		String className = null;
		if (ele.hasAttribute(CLASS_ATTRIBUTE)) {  //  标签中是否存在class元素
			className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
		}
		String parent = null;   //  标签中是否存在parent元素, 是否有有父级类
		if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
			parent = ele.getAttribute(PARENT_ATTRIBUTE);
		}

		try {
			// 创建一个GenericBeanDefinition,并赋值parentName和beanClass属性
			AbstractBeanDefinition bd = createBeanDefinition(className, parent);

			// ★ 核心: 解析每个标签中的属性元素
			parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);

			// 如果ele中存在description元素,那么将此元素的信息做为BeanDefinition的description属性信息
			bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DEscriptION_ELEMENT));

			parsemetaElements(ele, bd);
			parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
			parseReplacedMethodSubElements(ele, bd.getMethodOverrides());

			// ★ 核心: 解析标签的子标签, 定义构造函数来实现注入
			parseConstructorArgElements(ele, bd);
			// ★ 核心: 解析标签下的属性标签信息
			parsePropertyElements(ele, bd);
			// ★ 核心: 解析标签下的属性标签信息
			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;
	}

6-1 解析 标签中的元素信息, 见源码: BeanDefinitionParserDelegate.parseBeanDefinitionAttributes()

   /**
	 * 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) {

		// 标签中是否存在singleton元素, singleton元素已经被scope替代了
		if (ele.hasAttribute(SINGLETON_ATTRIBUTE)) {
			error("Old 1.x 'singleton' attribute in use - upgrade to 'scope' declaration", ele);
		}

		// 标签中是否存在scope元素, scope可定义当前Bean是原型的还是单例的,不配置,默认单例
		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.
			bd.setScope(containingBean.getScope());
		}

		// 标签中是否存在abstract元素,可定义当前Bean是否为抽象类,默认false
		if (ele.hasAttribute(ABSTRACT_ATTRIBUTE)) {
			bd.setAbstract(TRUE_VALUE.equals(ele.getAttribute(ABSTRACT_ATTRIBUTE)));
		}

		// 标签中是否存在lazy-init元素,可定义当前Bean是否懒加载,默认false
		String lazyInit = ele.getAttribute(LAZY_INIT_ATTRIBUTE);
		if (isDefaultValue(lazyInit)) {
			lazyInit = this.defaults.getLazyInit();
		}
		bd.setLazyInit(TRUE_VALUE.equals(lazyInit));

		// 标签中是否存在autowire元素,可定义当前Bean的属性注入模型,byName / byType
		String autowire = ele.getAttribute(AUTOWIRE_ATTRIBUTE);
		bd.setAutowireMode(getAutowireMode(autowire));

		// 标签中是否存在depends-on元素,可定义当前Bean是否依赖了其他Bean, 允许依赖多个, 用",;" 分割
		// 如果依赖了其他Bean,那么必须等其他Bean创建完成,才可以继续创建当前Bean
		if (ele.hasAttribute(DEPENDS_ON_ATTRIBUTE)) {
			String dependsOn = ele.getAttribute(DEPENDS_ON_ATTRIBUTE);
			bd.setDependsOn(StringUtils.tokenizeToStringArray(dependsOn, MULTI_VALUE_ATTRIBUTE_DELIMITERS));
		}

		// 标签中是否存在autowire-candidate元素, 默认是default
		// 如果一个接口A,有两个实现类B和C, 类D依赖了接口A, 此时程序就不知道去走哪个实现类的逻辑,
		// 如果实现类B上添加autowire-candidate并设置为false,表示实现类B不参与注入
		// 那么程序就会直接走实现类C的逻辑
		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元素,默认值是true
		if (ele.hasAttribute(PRIMARY_ATTRIBUTE)) {
			bd.setPrimary(TRUE_VALUE.equals(ele.getAttribute(PRIMARY_ATTRIBUTE)));
		}

		// 标签中是否存在init-method元素, 可定义当前Bean的初始化方法
		// 没有设置,就是用默认的初始化方法
		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元素,可定义当前Bean的销毁方法
		// 将当前Bean打上销毁标签,当容器销毁时,会执行这个销毁的方法中的逻辑
		// 没有设置,就是用默认的初始化方法
		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元素,可定义BeanFactory创建指定Bean时用到的方法
		// 标签中是否存在factory-bean元素,可以定义当前Bean是由哪个BeanFactory来创建
		// 当使用BeanFactory去创建Bean对象时,如果创建Bean的方法是非静态的,那么就必须先创建BeanFactory实例,设置工厂Bean创建指定对象的方法
		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;
	}

6-2 解析标签的子标签
① 定义构造函数来实现注入, 可能会存在多个标签, 因为实例Bean的构造函数参数可能会存在多个, 每一个标签代表构造函数的一个参数,可用index属性来指定参数的注入顺序。然后循环解析标签, 调用BeanDefinitionParserDelegate.parseConstructorArgElement()方法去解析每一个标签中元素信息。
① 如果标签中使用了index来指定构造函数参数的注入顺序,那么index值就是对应构造函数参数的顺序; 如果没有使用index来指定注入顺序,那么标签的顺序就是构造函数参数的注入顺序; 标签中指定的参数值一般用value或者ref元素表示, 将参数值封装成ConstructorArgumentValues对象,并存储到对应的BeanDefinition中,且指定参数的索引顺序

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

		// 指定构造函数参数的注入顺序,那么index值就是对应构造函数参数的顺序
		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));
						// 获取标签中ref或者value元素的值,并封装成一个ConstructorArgumentValues对象
						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 {
							// 在BeanDefinition中ConstructorArgumentValues属性中,给构造函数的参数打上索引标签
							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 {
				// 记录每个构造函数注入点
				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();
			}
		}
	}

6-3 解析标签的子标签
① 此方式是直接使用标签对实例Bean中的属性直接赋值, 实例Bean中的属性可能存在多个,那么就有多个标签, 需要循环去调用BeanDefinitionParserDelegate.parsePropertyElement()方法去解析每一个标签中元素信息
② 通过标签中name元素去判断propertyValueList中是否已经存在属性值了,如果存在,表示通过spring的扩展点手动给属性赋过值了,无需覆盖赋值
③ 如果属性没有赋过值,那么将整个标签封装成独立的PropertyValue对象,然后封装到BeanDefinition中

   /**
	 * Parse a property element.
	 */
	public void parsePropertyElement(Element ele, BeanDefinition bd) {
		// 获取name元素信息, name一般指Bean对象中的属性名称,规范value是必须与属性名称一致
		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 {
			// 判断是否通过手动注入的方式已经给Bean中的属性赋过值了, 如果已经赋过值了,就不需要再次赋值了
			// 主要是通过Spring提供的扩展点来实现的
			if (bd.getPropertyValues().contains(propertyName)) {
				error("Multiple 'property' definitions for property '" + propertyName + "'", ele);
				return;
			}
			// 获取属性需要注入的值, 是value或者ref元素的值,并封装成PropertyValue对象
			Object val = parsePropertyValue(ele, bd, propertyName);
			PropertyValue pv = new PropertyValue(propertyName, val);
			parsemetaElements(ele, pv);
			pv.setSource(extractSource(ele));

			// 将PropertyValue对象存放到MutablePropertyValues对象中的propertyValueList属性里
			// 然后封装到BeanDefinition中
			bd.getPropertyValues().addPropertyValue(pv);
		} finally {
			this.parseState.pop();
		}
	}

6-4 解析标签下的属性标签信息
标签可能会定义多个,因为一个Bean中可能存在多个实例属性,每个实例属性都可能需要标签来定义。通过BeanDefinitionParserDelegate.parseQualifierElements()来解析每一个标签。
③ 通过 标签中的type属性,封装成一个AutowireCandidateQualifier对象

   /**
	 * Parse a qualifier element.
	 */
	public void parseQualifierElement(Element ele, AbstractBeanDefinition bd) {
		// 获取 标签中 type属性, 表示属性的类型
		String typeName = ele.getAttribute(TYPE_ATTRIBUTE);
		if (!StringUtils.hasLength(typeName)) {
			error("Tag 'qualifier' must have a 'type' attribute", ele);
			return;
		}
		// 记录注入点
		this.parseState.push(new QualifierEntry(typeName));
		try {
			AutowireCandidateQualifier qualifier = new AutowireCandidateQualifier(typeName);
			qualifier.setSource(extractSource(ele));

			// 获取 标签中 value属性, 表示属性的类型
			String value = ele.getAttribute(VALUE_ATTRIBUTE);
			if (StringUtils.hasLength(value)) {
				qualifier.setAttribute(AutowireCandidateQualifier.VALUE_KEY, value);
			}
			NodeList nl = ele.getChildNodes();
			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;
					}
				}
			}
			bd.addQualifier(qualifier);
		} finally {
			this.parseState.pop();
		}
	}
7. 当XML配置资源文件中的所有标签解析完后, 每一个标签对应一个Bean对象, 对应容器中的一个BeanDefition,此时的容器中存在多个BeanDefition, 然后对每一个BeanDefition创建对应的Bean实例对象, 就会进入Bean的生命周期环节。
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/737341.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号