Spring 源码第三弹!EntityResolver 是个什么鬼?

所以我们虽然在 Spring 的 XML 配置中看到的约束文件是一个在线地址,实际上约束文件是从本地 jar 中读取的。

2.两种解析器


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 类中:

  1. 首先通过两种不同的后缀来区分不同的约束。

  2. 然后定义了 dtdResolver 和 schemaResolver 两个不同的变量,对应的类型分别是 BeansDtdResolver 和 PluggableSchemaResolver,也就是 dtd 和 schema 的约束验证分别由这两个类来处理。

  3. 在 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();

}

}

  1. 在这个类中,一上来先通过 DEFAULT_SCHEMA_MAPPINGS_LOCATION 变量定义了 spring.schemas 文件的位置。

  2. getSchemaMappings 方法则是将 spring.schemas 文件中的内容读取成一个 Map 加载进来。

  3. 在 resolveEntity 方法中,根据 systemId 找到文件路径,systemId 是 http\://www.springframework.org/schema/beans/spring-beans.xsd 格式,文件路径则是 org/springframework/beans/factory/xml/spring-beans.xsd,如果第一次没有加载到,就把用户的 https: 替换成 http: 再去加载。

  4. 有了文件路径,接下来调用 ClassPathResource 去获取一个 Resource 对象,这块可以参考本系列第二篇,这里我就不再赘述。

  5. 最后构造一个 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 中通过资源的相对路径再次尝试加载。

3.小结


好啦,经过上面的介绍,相信大家对于 XMl 约束和 EntityResolver 都有一定的了解啦。

后记

Java高频面试专题合集解析:

阿里Java岗面试百题:Spring 缓存 JVM 微服务 数据库 RabbitMQ等

当然在这还有更多整理总结的Java进阶学习笔记和面试题未展示,其中囊括了Dubbo、Redis、Netty、zookeeper、Spring cloud、分布式、高并发等架构资料和完整的Java架构学习进阶导图!

阿里Java岗面试百题:Spring 缓存 JVM 微服务 数据库 RabbitMQ等

更多Java架构进阶资料展示

阿里Java岗面试百题:Spring 缓存 JVM 微服务 数据库 RabbitMQ等

阿里Java岗面试百题:Spring 缓存 JVM 微服务 数据库 RabbitMQ等

阿里Java岗面试百题:Spring 缓存 JVM 微服务 数据库 RabbitMQ等

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)]

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

  • 9
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值