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.
最后,感谢阅读,如有错误之处,请不吝指正。