spring源码04: XML中获取DOM树

上节讲到 xml文件 -> Resource -> InputStram,并且提到spring的一个有趣的规则,即以do开头的均为真正的核心逻辑。
本节继续跟踪XmlBeanDefinitionReader.java中的doLoadBeanDefinitions。即流程图的第3点,inputStream -> DOM树
spring解析阶段.jpg


inputStream -> DOM

  1. Xml文件读取示例
    在读源码之前,先复习一下XML文件的读取

解析xml文档一般有两种技术:
1.dom:Document Object Model(spring使用,因此我们只关心这种)
2.sax:Simple API for XML
参考:jaxp解析器用dom方式操作xml文档总结

代码的核心就只有加了注释的那3句,而且代码很固定,因此spring也是这么用的。当我们阅读源码到这段代码时便豁然开朗,原来spring源码也是人写出来的,和我写的好像也没什么区别

@Test
public void XmlReader() throws IOException, ParserConfigurationException, SAXException {

	InputStream inputStream = new ClassPathResource("spring-config.xml").getInputStream();

	/**
	 * 获取DocumentBuilderFactory
	 * 通过DocumentBuilderFactory获取DocumentBuilder
	 * 解析inputStream
	 */
	DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
	DocumentBuilder docBuilder = factory.newDocumentBuilder();
	Document doc = docBuilder.parse(inputStream);

	// 获取element并循环输出
	Element documentElement = doc.getDocumentElement();
	NodeList nodes = documentElement.getChildNodes();
	for (int i = 0; i < nodes.getLength(); i++) {
		Node node = nodes.item(i);
		NamedNodeMap attributes = node.getAttributes();
		if (null != attributes) {
			System.out.println(attributes.getNamedItem("id"));
			System.out.println(attributes.getNamedItem("class"));
		}
	}
}

// 输出
id="myBean"
class="jianshu1.MyBean"
  1. inputStream转化为dom树
// XmlBeanDefinitionReader.java
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
			throws BeanDefinitionStoreException {

		try {
			// 将文件流转化为DOM树
			Document doc = doLoadDocument(inputSource, resource);
			// DOM树转化为BeanDefinition,并注册到对应map中
			int count = registerBeanDefinitions(doc, resource);
			if (logger.isDebugEnabled()) {
				logger.debug("Loaded " + count + " bean definitions from " + resource);
			}
			return count;
		}
		catch () {
			throw ...;
		}
	
	}

又看到了熟悉的命名方式doLoadDocument,继续跟踪

// XmlBeanDefinitionReader.java
	protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
		/**
		 * getValidationModeForResource:获取验证模式DTD或者XSD
		 * getEntityResolver:获取检验文件DTD的方式,一般是从网络下载,但是通过EntityResolver由程序来定义寻找过程,
		 * 比如我们将DTD放在工程的某个文件夹下,这样就避免了通过网络的问题
		 */
		return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
				getValidationModeForResource(resource), isNamespaceAware());
	}

这段代码跟我们想要看到的不大一样,真正的核心代码在更里面一层,该层目的是准备好生成DocumentBuilder所需要的配置。例如:验证模式说明当前xml属于DTD或者XSD,EntityResolver等。这些参数有兴趣的自行查阅dom读取xml的API,我们这里只挑获取校验模式进行讲解。
3. 获取校验模式

// XmlBeanDefinitionReader.java
	protected int getValidationModeForResource(Resource resource) {
		// 如果用户手动配置了校验模式,则直接使用用户自定义校验模式
		int validationModeToUse = getValidationMode();
		if (validationModeToUse != VALIDATION_AUTO) {
			return validationModeToUse;
		}
		// 否则通过detectValidationMode自动检测获取
		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).
		// 还是没有则默认使用XSD模式
		return VALIDATION_XSD;
	}

下面的代码虽然多,但是都是在做校验,最关键的核心代码就最后一个函数hasDoctype
spring在做校验模式获取比我想象中要粗糙一些,只要文件包含"DOCTYPE"就是DTD否则就是XSD,也许这么判断已经做够了

// XmlBeanDefinitionReader.java
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);
		}
	}

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;
				}
				// 判断是否属于DTD模式
				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();
		}
	}

// 关键代码就这一行,只要文件当中包含"DOCTYPE"就是DTD,否则就是XSD
private boolean hasDoctype(String content) {
	// 判断是否包含DOCTYPE
	return content.contains(DOCTYPE);
}
  1. loadDocumen核心代码
    继续往里跳转,兜兜转转这么久,这一段就是inputSteram -> DOM树的真容,除了新加了一些配置参数以外,是不是跟我们写的一毛一样?
// XmlBeanDefinitionReader.java
	public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
			ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {

		// 获取DocumentBuilderFactory
		DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
		if (logger.isTraceEnabled()) {
			logger.trace("Using JAXP provider [" + factory.getClass().getName() + "]");
		}
		// 通过DocumentBuilderFactory获取DocumentBuilder
		DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
		// 解析inputStream
		return builder.parse(inputSource);
	}
发布了30 篇原创文章 · 获赞 9 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/chaitoudaren/article/details/104833480