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

上篇文章和小伙伴们说了 Spring 源码中 XML 文件的解析流程,本来可以继续往下走看加载核心类了,但是松哥还是希望能够慢一点,既然要学就学懂,在 XML 文件解析的过程中还涉及到一些其他的类和概念,因此我就先用几篇文章介绍一下这些涉及到的概念或者类,然后我们再继续往下看。

本文要和大家介绍的是上篇文章中涉及到的 EntityResolver 类,看看这个类到底是干嘛用的。

本文是 Spring 源码系列第四篇,阅读前面文章有助于更好理解本文:

  1. Spring 源码解读计划
  2. Spring 源码第一篇开整!配置文件是怎么加载的?
  3. Spring 源码第二弹!XML 文件解析流程

先来回顾下,在 EntityResolver 这个类在上篇文章哪里出现了。

我们在讲到 doLoadDocument 方法时,在该方法中调用 loadDocument 方法时,传递的第二个参数就是一个 EntityResolver 实例,当时我们说这个是用来处理文件的验证方式的,但是到底是怎么处理的,今天我们就来看下。

1.XML 验证模式

要了解 EntityResolver,就得先来看看 XML 文件验证模式。

现在我们大多数情况下可能都是使用 JSON 传递数据,XML 使用较少,可能有的小伙伴对 XML 文件的一些规则还不太熟悉,我这里稍微说一下。

XML 是指可扩展标记语言(eXtensible Markup Language),它是一种标记语言,类似 HTML;XML 标签没有被预定义,需要用户自行定义标签,也就是 XML 文件中的节点都是用户自定义的。XML 文件从设计之初就是为了传输数据,而非显示数据。

一般来说,一个 XML 文件由六个部分组成:

  • 文档生命
  • 元素
  • 属性
  • 注释
  • CDATA 区
  • 处理指令

虽然说 XML 文件本身是没有预定义 XML 标签,但是当 XML 文件作为框架的配置时,对于 XML 标签还是要有一定的约束,否则每个人都按照自己的喜好定义 XML 标签,框架就没法读取这样的 XML 文件了。

在 XML 技术中,开发者可以通过一个文档来约束一个 XML 的文档中的标签,这个文档称之为约束。遵循 XML 语法的 XML 我们称之为格式良好的 XML,而遵循 XML 约束的 XML 我们称之为有效的 XML。XML 约束文档主要定义了在 XML 中允许出现的元素名称、属性及元素出现的顺序等等。

要想约束 XML 标签,有两种方式:

  1. DTD
  2. Schema

DTD(Document Type Definition),全称为文档类型定义,一个 DTD 约束文件我们既可以定义在 XML 文件内部,也可以定义一个本地文件,也可以引用一个网络上的公共的 DTD。

XML Schema 也是一种用于定义和描述 XML 文档结构与内容的模式语言,相比于 DTD,Schema 对于名称空间的支持更加友好,同时也支持更多的数据类型,而且它的约束能力也比较强大,另外还有非常重要的一点是,Schema 文档本身也是 XML 文档,而不是像 DTD 一样使用自成一体的语法。

所以,Schema 目前在 XML 约束这块更具备优势,也在逐渐替代 DTD。

大家在日常开发中,这两种约束可能都见过,但是有的人可能没注意。我给大家简单举一个例子。

早期的 Spring 配置头部是这样的(Spring2.x),这就是 DTD 约束:

<?xml  version="1.0" encoding="UTF-8"?>  beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN"  "http://www.springframework.org/dtd/spring-beans-2.0.dtd">        

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

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

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

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

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

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

d2486f3398b2f554e2069fe434cf98b4.png

spring.handlers 文件内容如下:

http://www.springframework.org/schema/c=org.springframework.beans.factory.xml.SimpleConstructorNamespaceHandlerhttp://www.springframework.org/schema/p=org.springframework.beans.factory.xml.SimplePropertyNamespaceHandlerhttp://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.xsdhttp://www.springframework.org/schema/beans/spring-beans-2.5.xsd=org/springframework/beans/factory/xml/spring-beans.xsdhttp://www.springframework.org/schema/beans/spring-beans-3.0.xsd=org/springframework/beans/factory/xml/spring-beans.xsdhttp://www.springframework.org/schema/beans/spring-beans-3.1.xsd=org/springframework/beans/factory/xml/spring-beans.xsdhttp://www.springframework.org/schema/beans/spring-beans-3.2.xsd=org/springframework/beans/factory/xml/spring-beans.xsdhttp://www.springframework.org/schema/beans/spring-beans-4.0.xsd=org/springframework/beans/factory/xml/spring-beans.xsdhttp://www.springframework.org/schema/beans/spring-beans-4.1.xsd=org/springframework/beans/factory/xml/spring-beans.xsdhttp://www.springframework.org/schema/beans/spring-beans-4.2.xsd=org/springframework/beans/factory/xml/spring-beans.xsdhttp://www.springframework.org/schema/beans/spring-beans-4.3.xsd=org/springframework/beans/factory/xml/spring-beans.xsdhttp://www.springframework.org/schema/beans/spring-beans.xsd=org/springframework/beans/factory/xml/spring-beans.xsd

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

9697ea7368ef70826eaab0e493806aa2.png

所以我们虽然在 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 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 getSchemaMappings() {  Map 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 都有一定的了解啦。

后记

本文刚写完,微信群里就有小伙伴问了一个一模一样的问题:

a94fa9a30805b77336dc3e89c8093ace.png

松哥不禁感叹,源码并非离我们很远的东西,阅读源码可以有效解决我们日常开发中一些实实在在的问题!

如果觉得有收获,记得点个在看鼓励下松哥哦~搜索微信公众号【江南一点雨】,回复 888 获取超 17k star 开源项目学习文档~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/472856.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

从RAID看垂直伸缩到水平伸缩的演化

learn from 从0开始学大数据&#xff08;极客时间&#xff09; 大规模数据存储问题&#xff1a; 容量问题&#xff0c;数据量超过磁盘容量读写速度&#xff0c;磁盘读写慢数据可靠性&#xff0c;磁盘寿命问题 RAID&#xff08;独立磁盘冗余阵列&#xff09; 是将多块普通磁盘…

linux安装g++编译器_Ubuntu Desktop下配置Rosetta安装教程

作者: 吴炜坤本文仅在虚拟机环境下测试&#xff0c;可能实际操作中会遇到不同的问题本文是新手向的安装教程&#xff0c;如果需要在CentOS上安装&#xff0c;可以参考本人其他安装教程由于许多新人朋友在学习Rosetta过程中&#xff0c;通常操作系统选择的都是带美丽漂亮界面便于…

HDFS依然是存储的王者

learn from 从0开始学大数据&#xff08;极客时间&#xff09; 1. HDFS 架构 DataNode 负责数据的存储、读写&#xff0c;HDFS 将文件分割成若干数据块&#xff08;Block&#xff09;&#xff0c;每个 DataNode 存储一部分数据块&#xff0c;文件就分布存储在整个 HDFS 服务器集…

DateTime和字符串转换问题

DateTime和string之间的相互转换经常碰到,可就这么简单的一个转换其中也有些需要注意的地方. 1 static void Main(string[] args)2 {3 string format "yyyy/MM/dd HH:mm:ss";4 DateTimeFormatInfo dtfi DateTimeFormatInf…

.net 5 正式版_.NET 5正式版快来了

微软已在5月19号发布了.NET 5.0的第四个预览版。什么是.NET 5.NET 5.0.NET 5.0是.NET Framework和.NET Core核心的结合&#xff0c;旨在统一.NET平台&#xff0c;微软将其描述为“.NET的未来”&#xff0c;正式版预计将于2020年11月10日发布。.NET 5.0的高级目标包括提供统一的…

天池 在线编程 矩阵还原(前缀和)

文章目录1. 题目2. 解题1. 题目 输入: 2 2 [[1,3],[4,10]] 输出: [[1,2],[3,4]]Explanation: before: 1 2 3 4after: 1 3 4 10https://tianchi.aliyun.com/oj/286606814880453210/327250187142763355 2. 解题 前缀和逆运算 class Solution { public:/*** param n: the row o…

input 输入事件_输入超时为例学习 Python 的线程和协程

需求&#xff1a;做一个程序等待用户输入&#xff0c;3秒内输入则会 echo 这个输入并立即退出。3秒内没输入则自动退出。实现方法&#xff1a;1. 线程&#xff08;错误示范&#xff09;import 首先启动两个线程&#xff0c;并把等待输入的 get_input 设置成 daemon。于是 3 秒后…

PHP,Mysql-根据一个给定经纬度的点,进行附近地点查询–合理利用算法,效率提高2125倍...

目前的工作是需要对用户的一些数据进行分析&#xff0c;每个用户都有若干条记录&#xff0c;每条记录中有用户的一个位置&#xff0c;是用经度和纬度表示的。 还有一个给定的数据库&#xff0c;存储的是一些已知地点以及他们的经纬度&#xff0c;内有43W多条的数据。 现在需要拿…

js固定表格行列_纯前端表格控件SpreadJS V14.0发布:组件化编辑器+数据透视表

SpreadJS 是一款基于 HTML5 的纯前端表格控件&#xff0c;兼容 450 种以上的 Excel 公式&#xff0c;具备“高性能、跨平台、与 Excel 高度兼容”的产品特性&#xff0c;可为用户提供高度类似 Excel 的功能&#xff0c;满足 Web Excel组件开发、 表格文档协同编辑、 数据填报、…

天池 在线编程 区间统计(队列)

文章目录1. 题目2. 解题1. 题目 给定一个01数组 arr 和 一个整数 k, 统计有多少区间符合如下条件: 区间的两个端点都为 0 (允许区间长度为1)区间内 1 的个数不多于 k arr 的大小不超过 10^5 样例 1: 输入: arr [0, 0, 1, 0, 1, 1, 0], k 1 输出: 7 解释: [0, 0], [1, 1],…

android 模糊查询控件_第三十二篇:在SOUI2.0中像android一样使用资源

SOUI2.0之前&#xff0c;在SOUI中使用资源通常是直接使用这个资源的name(一个字符串)来引用。使用字符串的好处在于字符串能够表达这个资源的意义&#xff0c;因此使用字符串也是现代UI引擎常用的方式。尽管直接使用字符串有意义明确的优点&#xff0c;它同样也有缺点&#xff…

天池 在线编程 有序队列

文章目录1. 题目2. 解题1. 题目 给出了一个由小写字母组成的字符串 S。 然后&#xff0c;我们可以进行任意次数的移动。 在每次移动中&#xff0c;我们选择前 K 个字母中的一个&#xff08;从左侧开始&#xff09;&#xff0c;将其从原位置移除&#xff0c;并放置在字符串的末…

网站搜索功能怎么实现_电商网站上的搜索功能是如何实现的?

今天是刘小爱自学Java的第159天。感谢你的观看&#xff0c;谢谢你。学习计划安排如下&#xff1a;索引库本质上和数据库类似&#xff0c;也是存储数据的&#xff0c;既然如此自然也会有增删改查。那么这个索引库到底有何特别应用呢&#xff1f;索引库的特别之处在于它的查询&am…

android蓝牙通信_Flutter通过BasicMessageChannel实现Flutter 与Android iOS 的双向通信

题记&#xff1a;——不到最后时刻&#xff0c;千万别轻言放弃&#xff0c;无论结局成功与否&#xff0c;只要你拼博过&#xff0c;尽力过&#xff0c;一切问心无愧。通过 Flutter 来进行移动应用开发&#xff0c;打包 Android 、iOS 双平台应用程序&#xff0c;在调用如相机、…

MapReduce既是编程模型又是计算框架

learn from 从0开始学大数据&#xff08;极客时间&#xff09; MapReduce 编程模型 包含 Map 和 Reduce 两个过程 map 的主要输入是一对 <Key, Value> 值&#xff0c;输出一对 <Key, Value> 值将相同 Key 合并&#xff0c;形成 <Key, Value 集合 >再将这个…

MapReduce 计算框架如何运作

learn from 从0开始学大数据&#xff08;极客时间&#xff09; 1. MapReduce 作业启动和运行机制 作业涉及三类关键进程&#xff1a; 大数据应用进程 这类进程是启动 MapReduce 程序的主入口&#xff0c;主要是指定 Map 和 Reduce 类、输入输出文件路径等&#xff0c;并提交作业…

linux忘记mysql密码_Linux下忘记Mysql密码的找回方法(图)

Mysql隔一段时间不访问&#xff0c;也许你会忘记访问密码&#xff0c;这时该怎么办&#xff0c;重装mysql吗&#xff1f;这个代价也太大了&#xff0c;我们这里介绍两种恢复密码的方法。方法一&#xff1a;因为Mysql密码存储于数据库mysql中的user表中&#xff0c;所以我们只要…

Yarn 资源调度框架

learn from 从0开始学大数据&#xff08;极客时间&#xff09; Hadoop 主要是由三部分组成&#xff1a; 分布式文件系统 HDFS分布式计算框架 MapReduce分布式集群资源调度框架 Yarn Yarn 的架构

mysql mtop 使用_MYSQLMTOP监控环境搭建

MySQLMTOP是一个由PythonPHP开发的MySQL企业级监控系统。系统由Python实现多进程数据采集和告警&#xff0c;PHP实现WEB展示和管理。最重要是MySQL服务器无需安装任何Agent&#xff0c;只需在监控WEB界面配置相关数据库信息功能非常强大&#xff1a;可对上百台MySQL数据库的状态…

Hive是如何让MapReduce实现SQL操作的?

learn from 从0开始学大数据&#xff08;极客时间&#xff09; 1. MapReduce 实现 SQL 的原理 SELECT pageid, age, count(1) FROM pv_users GROUP BY pageid, age;实现过程&#xff1a; 2. Hive 的架构 Hive 能够直接处理我们输入的 SQL 语句&#xff08;Hive SQL 语法与 标…