Spring源码学习(七)——Spring.Beans(四)

    上一篇说到loadBeanDefinitions这个方法主要干了三件事儿,第一件是获取xml文件的验证模式;第二件是加载xml文件,获得对应的Document;第三件是对bean信息进行注册。本篇先对第一件事进行介绍。

    在程序中如何判定某个文件是xml文件,只凭后缀肯定是不行的,只有对文件中的格式进行验证,才能证明确实是xml文件,这也为接下来的程序提供了保障。常用的验证模式有DTD验证和schema验证两种。

1.DTD验证模式

    DTD验证模式说白了,就是使用DTD文档中规定的格式对xml文件的格式进行验证。DTD文档可以分为内部DTD、外部DTD和内外结合DTD三种。DTD文档包含三个部分:元素的定义规则,元素之间的关系规则和属性的定义规则。

    1).内部DTD就是在xml文档内部嵌入DTD格式的内容,具体形式是:,比如:



  
  
  
]>

 小明
 
 24

可以看到,通过使用将DTD文档的内容包含在内,我们也可以看到xml文件的格式是根据DTD文档的定义来的。

    2}.外部DTD文档就是将DTD的内容放到另一个文件中,这样的用意在于内部DTD只能对嵌套的xml进行验证,而外部DTD可以对任何符合该DTD的xml进行验证,引用格式:。

    3).内外结合的DTD引用格式:,很显然,就是上面二者的结合。

    DTD文档自己拥有一套属于自己的语言,用来进行编写,如上面代码所示,我们可以通过这个语言实现元素、实体、属性的定义。我们可以通过DTD文档对xml文件进行很好的管理,但同时,DTD文档也有自己的局限性:首先,DTD文档有自己的一套语法,与xml的语法不同,这为我们带来了一定的不便;其次,DTD文档的数据类型有限,无法和数据库等的数据类型进行完全匹配;再次,DTD文档无法扩展,这种僵化的编程是我们不希望看到的;最后,DTD文档不支持命名空间,也就是说会产生命名冲突的问题,也就是说,在DTD文档中,每一个标签的名称都是唯一的,但在实际应用中,我们可以发现,同一个属性可能会被不同的实体所包含,但DTD由于不支持命名空间无法解决这一问题。

2.schema验证模式

    由于DTD存在着上述的局限性,人们开始寻求一种更好的方式来对xml文件进行定义和验证,schema文档就出现了。Schema验证模式又叫做xml Schema模式,也就是XSD模式。XSD模式就是利用xml Schema文档来对xml文件进行规定,它具有DTD的所有功能,同时又比DTD强大。前文的Demo中我们的spring config文件中就使用了xsd模式:




   我们重点关注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"

    第一行声明了schema的命名空间,第二行是所有schema文档的根命名空间,第三行标识了schema文档所在的位置。


		
		
			
				
				
					
					
					
					
				
				
			
			
				
					

上面是spring-beans.xsd的部分代码,可以看到xsd文件和xml文件的语法基本一样。

3.验证过程

    介绍完两种验证模式,继续回到doLoadBeanDefinition方法中。

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
			throws BeanDefinitionStoreException {
		try {
			int validationMode = getValidationModeForResource(resource);
			Document doc = this.documentLoader.loadDocument(
					inputSource, getEntityResolver(), this.errorHandler, validationMode, isNamespaceAware());
			return registerBeanDefinitions(doc, resource);
		}
		catch (BeanDefinitionStoreException ex) {
			throw ex;
		}
		catch (SAXParseException ex) {
			throw new XmlBeanDefinitionStoreException(resource.getDescription(),
					"Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex);
		}
		catch (SAXException ex) {
			throw new XmlBeanDefinitionStoreException(resource.getDescription(),
					"XML document from " + resource + " is invalid", ex);
		}
		catch (ParserConfigurationException ex) {
			throw new BeanDefinitionStoreException(resource.getDescription(),
					"Parser configuration exception parsing XML from " + resource, ex);
		}
		catch (IOException ex) {
			throw new BeanDefinitionStoreException(resource.getDescription(),
					"IOException parsing XML document from " + resource, ex);
		}
		catch (Throwable ex) {
			throw new BeanDefinitionStoreException(resource.getDescription(),
					"Unexpected exception parsing XML document from " + resource, ex);
		}
	}
 
	/**
	 * Gets the validation mode for the specified {@link Resource}. If no explicit
	 * validation mode has been configured then the validation mode is
	 * {@link #detectValidationMode detected}.
	 * 

Override this method if you would like full control over the validation * mode, even when something other than {@link #VALIDATION_AUTO} was set. */ protected int getValidationModeForResource(Resource resource) { int validationModeToUse = getValidationMode(); if (validationModeToUse != VALIDATION_AUTO) { return validationModeToUse; } int detectedMode = detectValidationMode(resource); if (detectedMode != VALIDATION_AUTO) { return detectedMode; } // Hmm, we didn't get a clear indication... Let's assume XSD, // since apparently no DTD declaration has been found up until // detection stopped (before finding the document's root tag). return VALIDATION_XSD; }

 

可以看到第一行就调用了getValidationModeForResource方法来对xml进行验证,进方法里可以看到,首先获取resource对象的validationMode属性,然后判断如果指定了验证模式,就选择相应的模式,否则就调用detectValidationMode方法对验证模式进行检测。进入这个方法:

 

protected int detectValidationMode(Resource resource) {
		if (resource.isOpen()) {
			throw new BeanDefinitionStoreException(
					"Passed-in Resource [" + resource + "] contains an open stream: " +
					"cannot determine validation mode automatically. Either pass in a Resource " +
					"that is able to create fresh streams, or explicitly specify the validationMode " +
					"on your XmlBeanDefinitionReader instance.");
		}

		InputStream inputStream;
		try {
			inputStream = resource.getInputStream();
		}
		catch (IOException ex) {
			throw new BeanDefinitionStoreException(
					"Unable to determine validation mode for [" + resource + "]: cannot open InputStream. " +
					"Did you attempt to load directly from a SAX InputSource without specifying the " +
					"validationMode on your XmlBeanDefinitionReader instance?", ex);
		}

		try {
			return this.validationModeDetector.detectValidationMode(inputStream);
		}
		catch (IOException ex) {
			throw new BeanDefinitionStoreException("Unable to determine validation mode for [" +
					resource + "]: an error occurred whilst reading from the InputStream.", ex);
		}
	}

可以看到,获取了resource对象的inputStream后,调用了detectValidationMode方法来进行探测,继续进去:

public int detectValidationMode(InputStream inputStream) throws IOException {
		// Peek into the file to look for DOCTYPE.
		BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
		try {
			boolean isDtdValidated = false;
			String content;
			while ((content = reader.readLine()) != null) {
				content = consumeCommentTokens(content);
				if (this.inComment || !StringUtils.hasText(content)) {
					continue;
				}
				if (hasDoctype(content)) {
					isDtdValidated = true;
					break;
				}
				if (hasOpeningTag(content)) {
					// End of meaningful data...
					break;
				}
			}
			return (isDtdValidated ? VALIDATION_DTD : VALIDATION_XSD);
		}
		catch (CharConversionException ex) {
			// Choked on some character encoding...
			// Leave the decision up to the caller.
			return VALIDATION_AUTO;
		}
		finally {
			reader.close();
		}
	}

    首先开启一个字节流用来读xml文件,然后调用consumeCommentTokens来对文件中的注释进行跳过,代码如下:

private String consumeCommentTokens(String line) {
		if (!line.contains(START_COMMENT) && !line.contains(END_COMMENT)) {
			return line;
		}
		while ((line = consume(line)) != null) {
			if (!this.inComment && !line.trim().startsWith(START_COMMENT)) {
				return line;
			}
		}
		return line;
	}

其中,START_COMMENT的值是"",如果没有注释,直接返回这一行;xmlValidationModeDetector维护了一个布尔变量inComment用来标识某一行是否在注释中,如果在,则置true,并返回注释的内容到上一层方法,上一层方法对文档遍历的逻辑会直接跳过这一行对下一行进行遍历。继续往下走。获取到的一行会首先判断是否含有DOC验证模式的标志:"DOCTYPE",如果有则直接返回DTD验证模式,如果没有,那么就读取xml文件的开始符号,因为我们在编写xml文件之前,一定会先确定验证模式。

private boolean hasOpeningTag(String content) {
		if (this.inComment) {
			return false;
		}
		int openTagIndex = content.indexOf('<');
		return (openTagIndex > -1 && (content.length() > openTagIndex + 1) &&
				Character.isLetter(content.charAt(openTagIndex + 1)));
	}

从代码里看到,为了获取xml文档的开始符号,先获取"<"的位置,保证它的位置在一行里面,然后判断它后面跟的是不是字母,这从而过滤了所有xml文件的第一行:

这一行是用来标识xml文件的版本和编码的,其中的<后面跟的是?,而xml文件的真正开头后面肯定跟的是字母。判断出来以后,返回相应的值就好了,最后探测的结果会在getValidationModeForResource中进行判定,只要不是DOC模式,就会返回XSD模式。至此,xml文件的模式验证过程结束。

你可能感兴趣的:(#,Spring源码学习)