spring在讀取配置文件加載bean定義的時候會用到一個方法如下:
/**
* Return the EntityResolver to use, building a default resolver
* if none specified.
*/
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;
}
方法註釋大致意思是創建一個可以使用的EntityResolver ,如果沒有指定的話創建一個默認的。
那麼這個EntityResolver 到底是什麼呢?
xml解析的時候會遇到你所配置的xml格式是否符合規範的問題,而sax解析,可以通過你在xml的聲明上配置的publicid 和 連接地址獲取配置文件需要執行的一些規範,考慮到網絡下載不穩定以及斷網等問題,大多數框架會在自己內部jar包內放一份文件,然後自定義類實現EntityResolver 接口,這樣代碼運行先從本地嘗試獲取規範文件,獲取不到纔會從網絡下載,具體可以這篇博客
回到上面的方法,可以看到,spring內部有兩種方式創建EntityResolver,但是點進源碼你會發現,
ResourceEntityResolver繼承了DelegatingEntityResolver,重寫的方法如下
@Override
@Nullable
public InputSource resolveEntity(String publicId, @Nullable String systemId) throws SAXException, IOException {
InputSource source = super.resolveEntity(publicId, systemId);
if (source == null && systemId != null) {
String resourcePath = null;
try {
String decodedSystemId = URLDecoder.decode(systemId, "UTF-8");
String givenUrl = new URL(decodedSystemId).toString();
String systemRootUrl = new File("").toURI().toURL().toString();
// Try relative to resource base if currently in system root.
if (givenUrl.startsWith(systemRootUrl)) {
resourcePath = givenUrl.substring(systemRootUrl.length());
}
}
catch (Exception ex) {
// Typically a MalformedURLException or AccessControlException.
if (logger.isDebugEnabled()) {
logger.debug("Could not resolve XML entity [" + systemId + "] against system root URL", ex);
}
// No URL (or no resolvable URL) -> try relative to resource base.
resourcePath = systemId;
}
if (resourcePath != null) {
if (logger.isTraceEnabled()) {
logger.trace("Trying to locate XML entity [" + systemId + "] as resource [" + resourcePath + "]");
}
Resource resource = this.resourceLoader.getResource(resourcePath);
source = new InputSource(resource.getInputStream());
source.setPublicId(publicId);
source.setSystemId(systemId);
if (logger.isDebugEnabled()) {
logger.debug("Found XML entity [" + systemId + "]: " + resource);
}
}
}
return source;
}
可以看到它先是調用父類的resolveEntity方法獲取inputSource ,如果調用父類獲取失敗,那麼首先會獲取項目運行的絕對路徑,然後判斷配置文件裏面配置的url是不是用項目絕對路徑開頭的,如果是的話,說明文件是根據項目名自定義配置的,那麼截取絕對路徑後面的路徑,即相對路徑,作爲resource,如果上面解碼配置文件裏面的路徑有異常或者無法獲取項目路徑,就會直接把這個文檔配置的systemId作爲resource。最後通過resourceLoader
獲取inputSource.
上面說到ResourceEntityResolver會調用 DelegatingEntityResolver裏面的resolveEntity方法,那麼在父類裏面這個方法做了什麼操作呢?
public class DelegatingEntityResolver implements EntityResolver {
/** Suffix for DTD files. */
public static final String DTD_SUFFIX = ".dtd";
/** Suffix for schema definition files. */
public static final String XSD_SUFFIX = ".xsd";
private final EntityResolver dtdResolver;
private final EntityResolver schemaResolver;
public DelegatingEntityResolver(@Nullable ClassLoader classLoader) {
this.dtdResolver = new BeansDtdResolver();
this.schemaResolver = new PluggableSchemaResolver(classLoader);
}
@Override
@Nullable
public InputSource resolveEntity(String publicId, @Nullable String systemId) throws SAXException, IOException {
if (systemId != null) {
if (systemId.endsWith(DTD_SUFFIX)) {
return this.dtdResolver.resolveEntity(publicId, systemId);
}
else if (systemId.endsWith(XSD_SUFFIX)) {
return this.schemaResolver.resolveEntity(publicId, systemId);
}
}
return null;
}
}
可見DelegatingEntityResolver中封裝了兩個EntityResolver,一個是代理DTD的BeansDtdResolver,另一個是代理xml schema的PluggableSchemaResolver,resolveEntity方法會根據systemid的後綴判斷是用哪個entityResolver校驗xml.
最後,感謝閱讀,如有錯誤之處,請不吝指正。