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

现在大家看到的 Spring 配置头部一般都是这样,这就是 Schema 约束:

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns=“http://www.springframework.org/schema/beans”

xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance”

xmlns:context=“http://www.springframework.org/schema/context”

xsi:schemaLocation=“http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd”>

schema 约束对命名空间有着很好的支持,命名空间可以防止命名冲突,schema 中的名称空间和约束文件都是成对出现的。

有了约束,XML 文件中该写什么不该写什么就固定下来了,这样框架才能成功解析出 XML 文件。

但是大家同时也发现了一个新的问题,无论是 DTD 还是 Schema 约束,给出的约束文件地址都是一个在线地址,这就意味着项目启动时必须能够访问到该在线地址,才能加载到约束文件,如果访问在线约束文件失败,那么项目启动也会失败。

为了解决这个问题,框架一般都是将约束文件放在本地的,在本地哪里呢?实际上就在你下载的 jar 包里。以 spring-beans 为例,在下载的 jar 包里有如下两个文件:

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: 再去加载。

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

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

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

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

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

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

总结

互联网大厂比较喜欢的人才特点:对技术有热情,强硬的技术基础实力;主动,善于团队协作,善于总结思考。无论是哪家公司,都很重视高并发高可用技术,重视基础,所以千万别小看任何知识。面试是一个双向选择的过程,不要抱着畏惧的心态去面试,不利于自己的发挥。同时看中的应该不止薪资,还要看你是不是真的喜欢这家公司,是不是能真的得到锻炼。其实我写了这么多,只是我自己的总结,并不一定适用于所有人,相信经过一些面试,大家都会有这些感触。

**另外本人还整理收藏了2021年多家公司面试知识点以及各种技术点整理 **

下面有部分截图希望能对大家有所帮助。

在这里插入图片描述
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
人才特点:对技术有热情,强硬的技术基础实力;主动,善于团队协作,善于总结思考。无论是哪家公司,都很重视高并发高可用技术,重视基础,所以千万别小看任何知识。面试是一个双向选择的过程,不要抱着畏惧的心态去面试,不利于自己的发挥。同时看中的应该不止薪资,还要看你是不是真的喜欢这家公司,是不是能真的得到锻炼。其实我写了这么多,只是我自己的总结,并不一定适用于所有人,相信经过一些面试,大家都会有这些感触。

**另外本人还整理收藏了2021年多家公司面试知识点以及各种技术点整理 **

下面有部分截图希望能对大家有所帮助。

[外链图片转存中…(img-cXipLfsm-1713397440932)]
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值