所以我们虽然在 Spring 的 XML 配置中看到的约束文件是一个在线地址,实际上约束文件是从本地 jar 中读取的。
EntityResolver 就是用来处理 XML 验证的。我们先来看下 EntityResolver 接口的定义:
public interface EntityResolver {
public abstract InputSource resolveEntity (String publicId,
String systemId)
throws SAXException, IOException;
}
接口中就只有一个方法,就是加载约束文件。在 Spring 中,EntityResolver 的实现类是 DelegatingEntityResolver:
public class DelegatingEntityResolver implements EntityResolver {
public static final String DTD_SUFFIX = “.dtd”;
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);
}
public DelegatingEntityResolver(EntityResolver dtdResolver, EntityResolver schemaResolver) {
this.dtdResolver = dtdResolver;
this.schemaResolver = schemaResolver;
}
@Override
@Nullable
public InputSource resolveEntity(@Nullable 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;
}
@Override
public String toString() {
return "EntityResolver delegating " + XSD_SUFFIX + " to " + this.schemaResolver +
" and " + DTD_SUFFIX + " to " + this.dtdResolver;
}
}
在 DelegatingEntityResolver 类中:
-
首先通过两种不同的后缀来区分不同的约束。
-
然后定义了 dtdResolver 和 schemaResolver 两个不同的变量,对应的类型分别是 BeansDtdResolver 和 PluggableSchemaResolver,也就是 dtd 和 schema 的约束验证分别由这两个类来处理。
-
在 resolveEntity 方法中,根据解析出来不同的后缀,分别交由不同的 EntityResolver 来处理。resolveEntity 解析中有两个参数,如果是 dtd 解析的话,publicId 是有值的,如果是 schema 解析,publicId 为 null,而 systemId 则始终指向具体的约束文件。
由于现在大部分都是 schema 约束,所以这里我们就来重点看下 PluggableSchemaResolver 类的实现:
public class PluggableSchemaResolver implements EntityResolver {
public static final String DEFAULT_SCHEMA_MAPPINGS_LOCATION = “META-INF/spring.schemas”;
private static final Log logger = LogFactory.getLog(PluggableSchemaResolver.class);
@Nullable
private final ClassLoader classLoader;
private final String schemaMappingsLocation;
@Nullable
private volatile Map<String, String> schemaMappings;
public PluggableSchemaResolver(@Nullable ClassLoader classLoader) {
this.classLoader = classLoader;
this.schemaMappingsLocation = DEFAULT_SCHEMA_MAPPINGS_LOCATION;
}
public PluggableSchemaResolver(@Nullable ClassLoader classLoader, String schemaMappingsLocation) {
Assert.hasText(schemaMappingsLocation, “‘schemaMappingsLocation’ must not be empty”);
this.classLoader = classLoader;
this.schemaMappingsLocation = schemaMappingsLocation;
}
@Override
@Nullable
public InputSource resolveEntity(@Nullable String publicId, @Nullable String systemId) throws IOException {
if (logger.isTraceEnabled()) {
logger.trace(“Trying to resolve XML entity with public id [” + publicId +
“] and system id [” + systemId + “]”);
}
if (systemId != null) {
String resourceLocation = getSchemaMappings().get(systemId);
if (resourceLocation == null && systemId.startsWith(“https:”)) {
resourceLocation = getSchemaMappings().get(“http:” + systemId.substring(6));
}
if (resourceLocation != null) {
Resource resource = new ClassPathResource(resourceLocation, this.classLoader);
try {
InputSource source = new InputSource(resource.getInputStream());
source.setPublicId(publicId);
source.setSystemId(systemId);
if (logger.isTraceEnabled()) {
logger.trace(“Found XML schema [” + systemId + "] in classpath: " + resourceLocation);
}
return source;
}
catch (FileNotFoundException ex) {
if (logger.isDebugEnabled()) {
logger.debug(“Could not find XML schema [” + systemId + "]: " + resource, ex);
}
}
}
}
return null;
}
private Map<String, String> getSchemaMappings() {
Map<String, String> schemaMappings = this.schemaMappings;
if (schemaMappings == null) {
synchronized (this) {
schemaMappings = this.schemaMappings;
if (schemaMappings == null) {
try {
Properties mappings =
PropertiesLoaderUtils.loadAllProperties(this.schemaMappingsLocation, this.classLoader);
schemaMappings = new ConcurrentHashMap<>(mappings.size());
CollectionUtils.mergePropertiesIntoMap(mappings, schemaMappings);
this.schemaMappings = schemaMappings;
}
catch (IOException ex) {
throw new IllegalStateException(
“Unable to load schema mappings from location [” + this.schemaMappingsLocation + “]”, ex);
}
}
}
}
return schemaMappings;
}
@Override
public String toString() {
return "EntityResolver using schema mappings " + getSchemaMappings();
}
}
-
在这个类中,一上来先通过 DEFAULT_SCHEMA_MAPPINGS_LOCATION 变量定义了 spring.schemas 文件的位置。
-
getSchemaMappings 方法则是将 spring.schemas 文件中的内容读取成一个 Map 加载进来。
-
在 resolveEntity 方法中,根据 systemId 找到文件路径,systemId 是
http\://www.springframework.org/schema/beans/spring-beans.xsd
格式,文件路径则是org/springframework/beans/factory/xml/spring-beans.xsd
,如果第一次没有加载到,就把用户的https:
替换成http:
再去加载。 -
有了文件路径,接下来调用 ClassPathResource 去获取一个 Resource 对象,这块可以参考本系列第二篇,这里我就不再赘述。
-
最后构造一个 InputSource 返回即可。
在上篇文章中,我们获取 EntityResolver 是通过 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;
}
这里最终返回的是 ResourceEntityResolver,ResourceEntityResolver 继承自 DelegatingEntityResolver,当调用 resolveEntity 方法时,也是先调用父类的该方法,进行处理,如果父类方法处理成功了,就直接返回父类方法给出的结果,如果父类方法处理失败了,则在 ResourceEntityResolver 中通过资源的相对路径再次尝试加载。
好啦,经过上面的介绍,相信大家对于 XMl 约束和 EntityResolver 都有一定的了解啦。
后记
Java高频面试专题合集解析:
当然在这还有更多整理总结的Java进阶学习笔记和面试题未展示,其中囊括了Dubbo、Redis、Netty、zookeeper、Spring cloud、分布式、高并发等架构资料和完整的Java架构学习进阶导图!
更多Java架构进阶资料展示
ava高频面试专题合集解析:**
[外链图片转存中…(img-mOWKlA5l-1714457744468)]
当然在这还有更多整理总结的Java进阶学习笔记和面试题未展示,其中囊括了Dubbo、Redis、Netty、zookeeper、Spring cloud、分布式、高并发等架构资料和完整的Java架构学习进阶导图!
[外链图片转存中…(img-0sNXKH7q-1714457744469)]
更多Java架构进阶资料展示
[外链图片转存中…(img-CO3udtev-1714457744470)]
[外链图片转存中…(img-jp65xJ3Y-1714457744470)]
[外链图片转存中…(img-uIce3k4t-1714457744471)]