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 }
