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

spring.handlers 文件内容如下:

http://www.springframework.org/schema/c=org.springframework.beans.factory.xml.SimpleConstructorNamespaceHandler

http://www.springframework.org/schema/p=org.springframework.beans.factory.xml.SimplePropertyNamespaceHandler

http://www.springframework.org/schema/util=org.springframework.beans.factory.xml.UtilNamespaceHandler

这其实一个映射配置,每一个名称空间对应的处理类在这里进行配置。

spring.schemas 文件内容如下(部分):

http://www.springframework.org/schema/beans/spring-beans-2.0.xsd=org/springframework/beans/factory/xml/spring-beans.xsd

http://www.springframework.org/schema/beans/spring-beans-2.5.xsd=org/springframework/beans/factory/xml/spring-beans.xsd

http://www.springframework.org/schema/beans/spring-beans-3.0.xsd=org/springframework/beans/factory/xml/spring-beans.xsd

http://www.springframework.org/schema/beans/spring-beans-3.1.xsd=org/springframework/beans/factory/xml/spring-beans.xsd

http://www.springframework.org/schema/beans/spring-beans-3.2.xsd=org/springframework/beans/factory/xml/spring-beans.xsd

http://www.springframework.org/schema/beans/spring-beans-4.0.xsd=org/springframework/beans/factory/xml/spring-beans.xsd

http://www.springframework.org/schema/beans/spring-beans-4.1.xsd=org/springframework/beans/factory/xml/spring-beans.xsd

http://www.springframework.org/schema/beans/spring-beans-4.2.xsd=org/springframework/beans/factory/xml/spring-beans.xsd

http://www.springframework.org/schema/beans/spring-beans-4.3.xsd=org/springframework/beans/factory/xml/spring-beans.xsd

http://www.springframework.org/schema/beans/spring-beans.xsd=org/springframework/beans/factory/xml/spring-beans.xsd

可以看到,各种版本以及没有版本号的约束文件,都对应了同一个文件,就是 org/springframework/beans/factory/xml/spring-beans.xsd,打开这个文件目录,我们就可以看到约束文件:

所以我们虽然在 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 中通过资源的相对路径再次尝试加载。

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
大家的负担。**

[外链图片转存中…(img-t46GVSuA-1715064567731)]

[外链图片转存中…(img-IkKLLIWT-1715064567731)]

[外链图片转存中…(img-Qxy5xdVB-1715064567732)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值