1.1 何为 EntityResolver :
官方解释: 如果SAX应用程序叙事实现自定义处理外部实体,则必须实现此接口,
并使用setEntityResolver方法向SAX 驱动器注册一个实例.
也就是说,对于解析一个xml,sax
首先会读取该xml文档上的声明,根据声明去寻找相应的dtd定义,以便对文档的进行验证,
默认的寻找规则,(即:通过网络,实现上就是声明DTD的地址URI地址来下载DTD声明),
并进行认证,下载的过程是一个漫长的过程,而且当网络不可用时,这里会报错,就是应为相应的dtd没找到,
1.2 EntityResolver 的作用就是项目本身就可以提供一个如何寻找DtD 的声明方法,
即:由程序来实现寻找DTD声明的过程,比如我们将DTD放在项目的某处在实现时直接将此文档读取并返回个SAX即可,这样就避免了通过网络来寻找DTD的声明
1.3 首先看看EntityResolver 接口声明的方法.
public abstract InputSource resolveEntity (String publicId, String systemId) throws SAXException, IOException;
这里,他接收2个参数,publicId ,systemId ,并返回一个InputStream对象,
这里我们以特定的文件来讲解;
1.3.1 如果我们在解析验证模式为xsd的配置文件,代码如下
<?xml version="1.0" encoding="UTF-8"?> <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-2.5.xsd"> ... </beans>
读取得到以下参数,
publicId : null
systemId : http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
1.3.2 如果我们解析的是DTD的配置文件;
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <beans> ... </beans>
读取得到以下参数:
publicId : -//SPRING//DTD BEAN//EN
systemId : http://www.springframework.org/dtd/spring-beans.dtd
之前已经提到过,默认的寻找规则,(即:通过网络,实现上就是声明DTD的地址URI地址来下载DTD声明),这样才会造成延迟,用户体验也不好,一般的做法是将验证文件放在自己的工程里面,
那么怎么做才能将这个Url转换为自己工程里对应的文件呢?我们已加载DTD文件为例看看Spring中是如通过getEntityResolver() 对 EntityResolver 的获取,
1.4 DelegatingEntityResolver
我们知道Spring中使用DelegatingEntityResolver 类为 EntityResolver的实现类,
EntityResolver 的实现方法如下
1 @Override 2 public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException { 3 if (systemId != null) { 4 // 如果是DTD从这里开始 5 if (systemId.endsWith(DTD_SUFFIX)) { 6 return this.dtdResolver.resolveEntity(publicId, systemId); 7 } 8 // 如果是XSD从这里开始 9 else if (systemId.endsWith(XSD_SUFFIX)) { 10 // 通过调用META-INF/Spring.schemas解析 11 return this.schemaResolver.resolveEntity(publicId, systemId); 12 } 13 } 14 return null; 15 }
我们可以看到,对不同的验证模式,Spring用了不同的解析器,这里简单描述一下原理,
1.4.1 BeanDtdResolver
比如加载DTD类型的 BeanDtdResolver 的 resolveEntity是直接截取systemId最后的xxx.dtd ,然后去当前路径下寻找,代码如下:
1 @Override 2 public InputSource resolveEntity(String publicId, String systemId) throws IOException { 3 if (logger.isTraceEnabled()) { 4 logger.trace("Trying to resolve XML entity with public ID [" + publicId + 5 "] and system ID [" + systemId + "]"); 6 } 7 //截取systemId最后的xxx.dtd 8 if (systemId != null && systemId.endsWith(DTD_EXTENSION)) { 9 int lastPathSeparator = systemId.lastIndexOf("/"); 10 for (String DTD_NAME : DTD_NAMES) { 11 int dtdNameStart = systemId.indexOf(DTD_NAME); 12 if (dtdNameStart > lastPathSeparator) { 13 String dtdFile = systemId.substring(dtdNameStart); 14 if (logger.isTraceEnabled()) { 15 logger.trace("Trying to locate [" + dtdFile + "] in Spring jar"); 16 } 17 try { 18 Resource resource = new ClassPathResource(dtdFile, getClass()); 19 InputSource source = new InputSource(resource.getInputStream()); 20 source.setPublicId(publicId); 21 source.setSystemId(systemId); 22 if (logger.isDebugEnabled()) { 23 logger.debug("Found beans DTD [" + systemId + "] in classpath: " + dtdFile); 24 } 25 return source; 26 } 27 catch (IOException ex) { 28 if (logger.isDebugEnabled()) { 29 logger.debug("Could not resolve beans DTD [" + systemId + "]: not found in class path", ex); 30 } 31 } 32 33 } 34 } 35 }
1.4.2 PluggableSchemaResolver
而加载XSD类型的PluggableSchemaResolver 类的 resolveEntity 是默认到META-INF/Spring.schemas文件中找到systemid 对应的XSD文件并加载代码如下
1 @Override 2 public InputSource resolveEntity(String publicId, String systemId) throws IOException { 3 if (logger.isTraceEnabled()) { 4 logger.trace("Trying to resolve XML entity with public id [" + publicId + 5 "] and system id [" + systemId + "]"); 6 } 7 8 if (systemId != null) { 9 //获取 systemId 对应的XSD文件 10 String resourceLocation = getSchemaMappings().get(systemId); 11 if (resourceLocation != null) { 12 Resource resource = new ClassPathResource(resourceLocation, this.classLoader); 13 try { 14 InputSource source = new InputSource(resource.getInputStream()); 15 source.setPublicId(publicId); 16 source.setSystemId(systemId); 17 if (logger.isDebugEnabled()) { 18 logger.debug("Found XML schema [" + systemId + "] in classpath: " + resourceLocation); 19 } 20 return source; 21 } 22 catch (FileNotFoundException ex) { 23 if (logger.isDebugEnabled()) { 24 logger.debug("Couldn't find XML schema [" + systemId + "]: " + resource, ex); 25 } 26 } 27 } 28 } 29 return null; 30 }