上一篇文章已经介绍了Spring中XML的验证方式.接着讲Spring是如何把配置文件解析成Document的.
Document
@Override
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
// 1、创建DocumentBuidlerFactory
DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
if (logger.isTraceEnabled()) {
logger.trace("Using JAXP provider [" + factory.getClass().getName() + "]");
}
// 2、通过DocumentBuidlerFactory创建DocumentBuilder
DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
// 3、通过DocumentBuilder创建Document
return builder.parse(inputSource);
}
因为通过SAX解析XML文档的套路大致都差不多,所以这里不再多说.
但是该方法有一个参数需要注意:entityResolver
这里再贴一遍进入这个方法之前的代码:
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
getValidationModeForResource(resource), isNamespaceAware());
}
可以看到entityResolver是通过getEntityResolver来获取的.
那什么是entityResolver?
EntityResolver
官网:如果SAX应用程序需要实现自定义处理外部实体,则必须实现此接口并且使用setEntityResolver方法向SAX驱动器注册一个实体.
也就是说,对于解析一个XML文件,SAX首先读取该XML文档上的声明,根据声明去寻找相应的DTD定义,以便对文档进行一个验证.
而默认的寻找规则是通过网络(就是通过声明的DTD的URI地址)下载相应的DTD声明,并进行认证.但是因为下载的过程中可能因为网络中断或不可用,导致这里报错,因为她会认为你的DTD声明没有被找到.
EntityResolver的作用是项目本身就可以提供一个如何寻找DTD声明的方法,即由程序来实现寻找DTD声明的过程,比如我们把这个DTD文件放到项目中某处,在实现时直接将此文档读取并返回给SAX即可.
下面逐一介绍EntityResolver的获取和使用.
1、getEntityResolver()的代码如下:
protected EntityResolver getEntityResolver() {
if (this.entityResolver == null) {
// Determine default EntityResolver to use.
ResourceLoader resourceLoader = getResourceLoader();
if (resourceLoader != null) {
this.entityResolver = new ResourceEntityResolver(resourceLoader);
}
else {
this.entityResolver = new DelegatingEntityResolver(getBeanClassLoader());
}
}
return this.entityResolver;
}
2、将URL转化为自己工程中对应的文件地址
先介绍下entityResolver的工作原理
接口方法声明
public abstract InputSource resolveEntity (String publicId,
String systemId)
throws SAXException, IOException;
注意这里有两个参数:publicId
、systemId
分别使用两种方式的验证模式进行说明,这两个参数接受值.
- 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>
那么两个参数的值为:
- publicId:null
- systemId:https://maven.apache.org/xsd/maven-4.0.0.xsd
- DTD模式的配置文件
<?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>
那么两个参数的值为:
- publicId: -//mybatis.org//DTD Mapper 3.0//EN
- systemId: http://mybatis.org/dtd/mybatis-3-mapper.dtd
那么Spring是如何把URL转化为自己工程里文件的呢?
根据前面获取entityResolver的代码,可以知道,EntityResolver的实现类中有一个DelegatingEntityResolver.那么看一下这个实现类中的resolveEntity.
public InputSource resolveEntity(@Nullable String publicId, @Nullable String systemId)
throws SAXException, IOException {
// 通过上面的原理分析,不管是XSD还是DTD,systemId都不为空
if (systemId != null) {
// 判断systemId是否是以dtd后缀结尾
if (systemId.endsWith(DTD_SUFFIX)) {
// 如果是则使用 dtdResolver解析dtd,
// 直接截取systemId最后的xx.dtd,然后去当前路径下寻找
return this.dtdResolver.resolveEntity(publicId, systemId);
}
// 是否是以xsd结尾
else if (systemId.endsWith(XSD_SUFFIX)) {
// 如果是则使用schemaResolver解析
//通过调用META-INF/spring.schemas文件中找到systemid所对应的XSD文件
return this.schemaResolver.resolveEntity(publicId, systemId);
}
}
// Fall back to the parser's default behavior.
return null;
}