欢迎您访问365答案网,请分享给你的朋友!
生活常识 学习资料

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

时间:2023-06-08

在学习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 */@Overrideprotected 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 文档中解析出BeanDefinitionreturn 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. */@Nullablepublic 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是否为抽象类,默认falseif (ele.hasAttribute(ABSTRACT_ATTRIBUTE)) {bd.setAbstract(TRUE_VALUE.equals(ele.getAttribute(ABSTRACT_ATTRIBUTE)));}// 标签中是否存在lazy-init元素,可定义当前Bean是否懒加载,默认falseString lazyInit = ele.getAttribute(LAZY_INIT_ATTRIBUTE);if (isDefaultValue(lazyInit)) {lazyInit = this.defaults.getLazyInit();}bd.setLazyInit(TRUE_VALUE.equals(lazyInit));// 标签中是否存在autowire元素,可定义当前Bean的属性注入模型,byName / byTypeString autowire = ele.getAttribute(AUTOWIRE_ATTRIBUTE);bd.setAutowireMode(getAutowireMode(autowire));// 标签中是否存在depends-on元素,可定义当前Bean是否依赖了其他Bean, 允许依赖多个, 用",;" 分割// 如果依赖了其他Bean,那么必须等其他Bean创建完成,才可以继续创建当前Beanif (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元素,默认值是trueif (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的生命周期环节。

Copyright © 2016-2020 www.365daan.com All Rights Reserved. 365答案网 版权所有 备案号:

部分内容来自互联网,版权归原作者所有,如有冒犯请联系我们,我们将在三个工作时内妥善处理。