在上篇博客【死磕 Spring】—— IoC 之加载 Definitions 中提到,在核心逻辑方法 #doLoadBeanDefinitions(InputSource inputSource, Resource resource)
方法中,中主要是做三件事情:
- 调用
#getValidationModeForResource(Resource resource)
方法,获取指定资源(xml)的验证模式。 - 调用
DocumentLoader#loadDocument(InputSource inputSource, EntityResolver entityResolver,ErrorHandler errorHandler, int validationMode, boolean namespaceAware)
方法,获取 XML Document 实例。 - 调用
#registerBeanDefinitions(Document doc, Resource resource)
方法,根据获取的 Document 实例,注册 Bean 信息。
这篇博客主要第 1 步,分析获取 xml 文件的验证模式。为什么需要获取验证模式呢?原因如下:
XML 文件的验证模式保证了 XML 文件的正确性。
1. DTD 与 XSD 的区别
1.1 DTD
老艿艿:因为一些胖友,可能没了解过 DTD 和 XSD ,所以本小节先做一个科普。
DTD(Document Type Definition),即文档类型定义,为 XML 文件的验证机制,属于 XML 文件中组成的一部分。DTD 是一种保证 XML 文档格式正确的有效验证方式,它定义了相关 XML 文档的元素、属性、排列方式、元素的内容类型以及元素的层次结构。其实 DTD 就相当于 XML 中的 “词汇”和“语法”,我们可以通过比较 XML 文件和 DTD 文件 来看文档是否符合规范,元素和标签使用是否正确。
要在 Spring 中使用 DTD,需要在 Spring XML 文件头部声明:
|
DTD 在一定的阶段推动了 XML 的发展,但是它本身存在着一些缺陷:
- 它没有使用 XML 格式,而是自己定义了一套格式,相对解析器的重用性较差;而且 DTD 的构建和访问没有标准的编程接口,因而解析器很难简单的解析 DTD 文档。
- DTD 对元素的类型限制较少;同时其他的约束力也叫弱。
- DTD 扩展能力较差。
- 基于正则表达式的 DTD 文档的描述能力有限。
1.2 XSD
针对 DTD 的缺陷,W3C 在 2001 年推出 XSD。XSD(XML Schemas Definition)即 XML Schema 语言。XML Schema 本身就是一个 XML文档,使用的是 XML 语法,因此可以很方便的解析 XSD 文档。相对于 DTD,XSD 具有如下优势:
- XML Schema 基于 XML ,没有专门的语法。
- XML Schema 可以象其他 XML 文件一样解析和处理。
- XML Schema 比 DTD 提供了更丰富的数据类型。
- XML Schema 提供可扩充的数据模型。
- XML Schema 支持综合命名空间。
- XML Schema 支持属性组。
2. getValidationModeForResource
|
-
<1>
处,调用#getValidationMode()
方法,获取指定的验证模式(validationMode
)。如果有手动指定,则直接返回。另外,对对于validationMode
属性的设置和获得的代码,代码如下:public void setValidationMode(int validationMode) { this.validationMode = validationMode; } public int getValidationMode() { return this.validationMode; }
-
<2>
处,调用#detectValidationMode(Resource resource)
方法,自动获取验证模式。代码如下:/** * XML 验证模式探测器 */ private final XmlValidationModeDetector validationModeDetector = new XmlValidationModeDetector(); protected int detectValidationMode(Resource resource) { // 不可读,抛出 BeanDefinitionStoreException 异常 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 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); } // <x> 获取相应的验证模式 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); } }
- 核心在于
<x>
处,调用XmlValidationModeDetector#detectValidationMode(InputStream inputStream)
方法,获取相应的验证模式。详细解析,见 「3. XmlValidationModeDetector」 中。
- 核心在于
<3>
处,使用VALIDATION_XSD
做为默认。
3. XmlValidationModeDetector
org.springframework.util.xml.XmlValidationModeDetector
,XML 验证模式探测器。
|
<0
> 处,从代码中看,主要是通过读取 XML 文件的内容,来进行自动判断。-
<1>
处,调用#hasDoctype(String content)
方法,判断内容中如果包含有"DOCTYPE
“ ,则为 DTD 验证模式。代码如下:/** * The token in a XML document that declares the DTD to use for validation * and thus that DTD validation is being used. */ private static final String DOCTYPE = "DOCTYPE"; private boolean hasDoctype(String content) { return content.contains(DOCTYPE); }
-
<2>
处,调用#hasOpeningTag(String content)
方法,判断如果这一行包含<
,并且<
紧跟着的是字幕,则为 XSD 验证模式。代码如下:/** * Does the supplied content contain an XML opening tag. If the parse state is currently * in an XML comment then this method always returns false. It is expected that all comment * tokens will have consumed for the supplied content before passing the remainder to this method. */ 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))); // < 后面的内容是字幕 }
<3>
处,如果发生 CharConversionException 异常,则为VALIDATION_AUTO
模式。-
关于
#consumeCommentTokens(String content)
方法,代码比较复杂。感兴趣的胖友可以看看。代码如下:/** * The token that indicates the start of an XML comment. */ private static final String START_COMMENT = "<!--"; /** * The token that indicates the end of an XML comment. */ private static final String END_COMMENT = "-->"; /** * Consumes all the leading comment data in the given String and returns the remaining content, which * may be empty since the supplied content might be all comment data. For our purposes it is only important * to strip leading comment content on a line since the first piece of non comment content will be either * the DOCTYPE declaration or the root element of the document. */ @Nullable private String consumeCommentTokens(String line) { // 非注释 if (!line.contains(START_COMMENT) && !line.contains(END_COMMENT)) { return line; } String currLine = line; while ((currLine = consume(currLine)) != null) { if (!this.inComment && !currLine.trim().startsWith(START_COMMENT)) { return currLine; } } return null; } /** * Consume the next comment token, update the "inComment" flag * and return the remaining content. */ @Nullable private String consume(String line) { int index = (this.inComment ? endComment(line) : startComment(line)); return (index == -1 ? null : line.substring(index)); } /** * Try to consume the {@link #START_COMMENT} token. * @see #commentToken(String, String, boolean) */ private int startComment(String line) { return commentToken(line, START_COMMENT, true); } private int endComment(String line) { return commentToken(line, END_COMMENT, false); } /** * Try to consume the supplied token against the supplied content and update the * in comment parse state to the supplied value. Returns the index into the content * which is after the token or -1 if the token is not found. */ private int commentToken(String line, String token, boolean inCommentIfPresent) { int index = line.indexOf(token); if (index > - 1) { this.inComment = inCommentIfPresent; } return (index == -1 ? index : index + token.length()); }
- ? 反正老艿艿没细看。哈哈哈哈。如果真看,如下两篇文章,有一定的辅助:
666. 彩蛋
好了,XML 文件的验证模式分析完毕。下篇,我们来分析 #doLoadBeanDefinitions(InputSource inputSource, Resource resource)
方法的第 2 个步骤:获取 Document 实例。