XML文件的验证模式保证了XML文件的正确性,而比较常用的验证模式有两种:DTD、XSD.
DTD与XSD的区别
一、DTD(Document Type Definition)即文档型定义,是一种XML约束模式语言,是XML文件的验证机制,是XML文件组成的一部分.他可以通过比较XML文档和DTD文件来看文档是否符合规范,元素和标签是否使用正确.
一个DTD文档包含:元素的定义规则,元素间关系的定义规则,元素可使用的属性,可使用的实体或符号规则.
DTD的引入分为两种方式:
内部引用
内部引用应当通过下面的语法包装在一个 DOCTYPE 声明中:
<!DOCTYPE 根元素 [元素声明]>
带有DTD的XMl文档实例
<?xml version="1.0"?>
// note是根元素
<!DOCTYPE note [
// 定义 note 元素有四个元素:"to、from、heading,、body"
<!ELEMENT note (to,from,heading,body)>
// to 元素为 "#PCDATA" 类型,以下类推
<!ELEMENT to (#PCDATA)>
<!ELEMENT from (#PCDATA)>
<!ELEMENT heading (#PCDATA)>
<!ELEMENT body (#PCDATA)>
]>
<note>
<to>George</to>
<from>John</from>
<heading>Reminder</heading>
<body>Don't forget the meeting!</body>
</note>
外部引用
外部引用应通过下面的语法被封装在一个 DOCTYPE 定义中:
1、创建myDtd.dtd
// SYSTEM 引用的是本地文件
// PUBLIC 引用的是文件的网络地址
<!DOCTYPE 根元素 SYSTEM/PUBLIC "文件名">
2、创建dtd文件
// 定义 note 元素有四个元素:"to、from、heading,、body"
<!ELEMENT note (to,from,heading,body)>
// to 元素为 "#PCDATA" 类型,以下类推
<!ELEMENT to (#PCDATA)>
<!ELEMENT from (#PCDATA)>
<!ELEMENT heading (#PCDATA)>
<!ELEMENT body (#PCDATA)>
3、引入文件
<?xml version="1.0"?>
<!DOCTYPE note SYSTEM "note.dtd">
<note>
<to>George</to>
<from>John</from>
<heading>Reminder</heading>
<body>Don't forget the meeting!</body>
</note>
这里只是举一个小的例子,如果感兴趣可以查看W3cSchool手册
Mapper.xml文件
下面用一个mapper的配置文件来看下,DTD文件的PUBLIC的使用:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="xx.xxx.xxx.xxxMapper">
</mapper>
具体mybatis-3-mapper.dtd部分如下:
<?xml version="1.0" encoding="UTF-8" ?>
<!ELEMENT mapper (cache-ref | cache | resultMap* | parameterMap* | sql* | insert* | update* | delete* | select* )+>
<!ATTLIST mapper
namespace CDATA #IMPLIED
>
<!ELEMENT cache-ref EMPTY>
<!ATTLIST cache-ref
namespace CDATA #REQUIRED
>
<!ELEMENT cache (property*)>
<!ATTLIST cache
type CDATA #IMPLIED
eviction CDATA #IMPLIED
flushInterval CDATA #IMPLIED
size CDATA #IMPLIED
readOnly CDATA #IMPLIED
blocking CDATA #IMPLIED
>
二、XSD(XML Schemas Definition)就是XML Schemas. XML Schemas描述了XML文档的结构.可以用个一个指定的XML Schemas来验证某个XML文档,以检查该XML文档是否符合其要求.文档设计者可以通过XML Schema指定XML文档所允许的结构和内容,并可据此检查XML文档是否有效.
用pom.xml文件来看一下XSD文件及用法
1、定义XSD文档
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns="http://maven.apache.org/POM/4.0.0" elementFormDefault="qualified" targetNamespace="http://maven.apache.org/POM/4.0.0">
<xs:element name="project" type="Model">
</xs:element name="project" type="Model">
</xs:schema>
xmlns:xs="http://www.w3.org/2001/XMLSchema"
:显示 schema 中用到的元素和数据类型来自命名空间 “http://www.w3.org/2001/XMLSchema”。同时它还规定了来自命名空间 “http://www.w3.org/2001/XMLSchema” 的元素和数据类型应该使用前缀 xs:表示.xmlns="http://maven.apache.org/POM/4.0.0"
:默认的命名空间,不同的xml文件的默认命名空间不同.targetNamespace="http://maven.apache.org/POM/4.0.0
:显示被此 schema 定义的元素 (project…) 来自命名空间: “http://maven.apache.org/POM/4.0.0”。elementFormDefault="qualified
:指出任何 XML 实例文档所使用的且在此 schema 中声明过的元素必须被命名空间限定.
2、使用XSD文档
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
</project>
xmlns
是是命名空间:xmlns=“http://maven.apache.org/POM/4.0.0”;和上面的默认命名空间对应xsi:schemaLocation
是用来指定命名空间所对应的XML Schema文档的存储位置.其中,他又分为两部分:一部分是名称空间的URI(http://maven.apache.org/POM/4.0.0);另一部分是该名称空间所标识的XML Schema文件位置或URL地址(https://maven.apache.org/xsd/maven-4.0.0.xsd).xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
,本xml文件中要用到“http://www.w3.org/2001/XMLSchema-instance”这个命名空间的元素,比如用来引入无命名空间schema文件noNamespaceSchemaLocation=“XXX”;以及引入自带命名空间的schema文件的schemaLocation="XXX"这些元素。这些元素是包含在xsi命名空间中的,所有的xml文件只要引用这些元素 就要引入xsi这个命名空间。xsi这三个字母不是硬性规定,只是大家都这么用,方便阅读而已。
当然有兴趣深入理解也是可以去查看[W3cSchool手册].(https://www.w3school.com.cn/schema/index.asp)
Spring验证模式的读取
上一小节提到Spring在读取配置文件的时候,把配置文件转成inputSource之后就是进行解析了.解析主要又分为两大步:1、将inputSource解析成document 2、然后进一步将document解析成BeanDefinition并注册.
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
// 解析成Document
Document doc = doLoadDocument(inputSource, resource);
// 进一步解析,并注册BeanDefinition
// count:是通过配置文件注册的BeanDefinition的个数
int count = registerBeanDefinitions(doc, resource);
return count;
}
catch (Exception ex) {
throw ex;
}
}
1、Document的解析
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
getValidationModeForResource(resource), isNamespaceAware());
}
doLoadDocument的方法里面其实又调了documentLoader.loadDocument()方法,我们可以发现这里:getValidationModeForResource(resource)
,这个方法的作用就是获取resource资源的验证模式的.
protected int getValidationModeForResource(Resource resource) {
// 获取 validationMode的值,如果设定了,并且不是自动检测模式,就直接返回
int validationModeToUse = getValidationMode();
if (validationModeToUse != VALIDATION_AUTO) {
return validationModeToUse;
}
// 自动检测模式,如果自动检测模式返回了具体的检验模式就直接返回
// 否则就返回XSD模式
int detectedMode = detectValidationMode(resource);
if (detectedMode != VALIDATION_AUTO) {
return detectedMode;
}
// 这里是说,因为没有得到明确的信息,来证明是哪种验证模式,所以先假设是XSD模式
return VALIDATION_XSD;
}
第一种情况是调用XmlBeanDefinitionReader的setValidationMode方法进行设置.
第二种是Spring根据配置文件是否包含DOCTYPE来判断,如果包含就是DTD,不包含就是XSD.
自动检测的代码
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
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 {
// XmlValidationModeDetector的detectValidationMode做验证
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.
// 上迷啊的英文就是源码给的注释,意思是找文件中是否包含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;
}
// 如果包含DOCTYPE,直接返回
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();
}
}