Spring5源码分析(008)——IoC篇之加载BeanDefinition:获取XML的验证模式 -- 转载

注:《Spring5源码分析》汇总可参考:Spring5源码分析(002)——博客汇总

作者: wpbxin
出处:https://www.cnblogs.com/wpbxin/p/13207581.html

原文: https://www.cnblogs.com/wpbxin/p/13207581.html
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。

上一篇《Spring5源码分析(007)——IoC篇之加载BeanDefinition总览》 中提到,加载 bean 的核心方法 doLoadBeanDefinitions

(InputSource inputSource, Resource resource) 中,分为3个步骤来进行:

  • 1、通过调用 getValidationModeForResource(Resource resource) 来获取指定 XML 资源的验证模式,也即是 xml 开头中见到的各种 DTD 和 XSD 了。
  • 2、通过调用 DocumentLoader.loadDocument(InputSource inputSource, EntityResolver entityResolver, ErrorHandler errorHandler, int validationMode, boolean namespaceAware) 来获取实际的 Document 实例。
  • 3、调用 registerBeanDefinitions(Document doc, Resource resource),根据 Document 解析和注册 BeanDefinition。

  本文主要介绍第1个步骤,也就是获取 XML 资源文件的验证模式,目录结构如下:

  • 1、为什么需要获取 XML 的验证模式
  • 2、DTD 和 XSD 的区别
    • 2.1、DTD
    • 2.2、XSD
  • 3、getValidationModeForResource(Resource resource)
  • 4、XmlValidationModeDetector.detectValidationMode(InputStream inputStream)
  • 5、总结
  • 6、参考

1、为什么需要获取 XML 的验证模式

  XML 文件的验证模式保证了 XML 文件的正确性。换句话说,只有符合验证模式的 XML 文件,才能根据约定进行正确的解析。这就跟 Java(或者其他编程语言)一样,需要有语法和词汇等进行约束和规范,只有遵守了,编译器才能正确编译一样,验证模式正是这样的约束和规范。比较常用的 XML 验证模式有两种:DTD 和 XSD。下面将分别进行介绍。

2、DTD 和 XSD 的区别

2.1、DTD

  DTD (Document Type Definition)即文挡类型定义,是一种 XML 约束模式语言,是 XML 文件的验证机制,属于 XML 文件组成的一部分。DTD 是一种保证 XML 文档格式正确的有效验证机制,可以通过比较 XML 文档和 DTD 文件来看文档是否符合规范,元素和标签使用是否正确。一个 DTD 文档包含:元素的定义规则、元素间关系的定义规则、元素可使用的属性、可使用的实体或符号规则。它定义了 XML 文档相关的元素、属性、实体、排列方式、元素的内容类型以及元素的层次结构。

  需要使用 DTD 验证模式时,可以在 XML 配置文件中增加如下代码(Spring-beans-2.0.dtd):

<?xml versioπ= "1.0" encod1ng="UTF8"?>
<!DOCTYPE beans PUBLIC "-//Spring//DTD BEAN 2.0//EN" "http://www.Springframework.org/dtd/Spring-beans-2.O.dtd">

PS:笔者接触过的 Spring 项目中未曾遇到过 DTD 相关配置 ^_^

  DTD 有一定的作用,但其设计本身有些缺陷(参考:DTD的局限性):

  • 语法结构:DTD 不遵守 XML 语法,它自定义了一套与 XML 文档实例不一样的语法结构,这导致解析策略(解析器,DOM、XPath等)难以重用
  • 元素类型:DTD 对元素类型支持有限,不能自由扩充,不利于XML数据交换场合验证,扩展性差
  • 文档结构:DTD中,所有元素、属性都是全局的,无法声明仅与上下文位置相关的元素或属性
  • 命名空间:DTD 不支持命名空间

2.2、XSD

  针对 DTD 的缺陷,W3C 在 2001 年推出 XSD(XML Schemas Definition),即 XML Schema 定义,来对 DTD 进行替代。XML Schema 本身就是一个 XML文档,使用的是 XML 语法,因此可以很方便地使用通用的 XML 解析器来解析 XSD 文档。相对于 DTD,XSD 具有如下优势:

  • XML Schema 基于 XML ,没有专门的语法。
  • XML Schema 可以象其他 XML 文件一样解析和处理。
  • XML Schema 比 DTD 提供了更丰富的数据类型。
  • XML Schema 提供可扩充的数据模型。
  • XML Schema 支持综合命名空间。
  • XML Schema 支持属性组。

XML中DTD,XSD的区别与应用

PS:这部分算是对 DTD 和 XSD 做了个大致的介绍,稍微了解即可 ^_^

3、getValidationModeForResource(Resource resource)

  回到 Spring 中用于获取指定 XML 资源文件的验证模式的方法 getValidationModeForResource(Resource resource) 上来:

/*** Indicates that the validation should be disabled.* 禁止验证模式*/
public static final int VALIDATION_NONE = XmlValidationModeDetector.VALIDATION_NONE;/*** Indicates that the validation mode should be detected automatically.* 自动检测验证模式*/
public static final int VALIDATION_AUTO = XmlValidationModeDetector.VALIDATION_AUTO;/*** Indicates that DTD validation should be used.* DTD 验证模式*/
public static final int VALIDATION_DTD = XmlValidationModeDetector.VALIDATION_DTD;/*** Indicates that XSD validation should be used.* XSD 验证模式*/
public static final int VALIDATION_XSD = XmlValidationModeDetector.VALIDATION_XSD;// 指定的验证模式,默认是自动检测
private int validationMode = VALIDATION_AUTO;/*** Determine the validation mode for the specified {@link Resource}.* If no explicit validation mode has been configured, then the validation* mode gets {@link #detectValidationMode detected} from the given resource.* <p>Override this method if you would like full control over the validation* mode, even when something other than {@link #VALIDATION_AUTO} was set.* <p>确定指定资源的验证模式。如果没有显式配置验证模式,则从给定资源检测获取验证模式。* <p>如果需要完全控制验证模式,请覆盖此方法,即使在设置了 VALIDATION_AUTO 以外的内容时也是如此。* @see #detectValidationMode*/
protected int getValidationModeForResource(Resource resource) {// 1、获取指定的验证模式int validationModeToUse = getValidationMode();// 如果显式指定了验证模式则使用指定的验证模式if (validationModeToUse != VALIDATION_AUTO) {return validationModeToUse;}// 2、如果未指定则使用自动检测int detectedMode = detectValidationMode(resource);if (detectedMode != VALIDATION_AUTO) {return detectedMode;}// 3、还是没有找到验证模式的显示声明,则最后默认使用 XSD 验证模式// Hmm, we didn't get a clear indication... Let's assume XSD,// since apparently no DTD declaration has been found up until// detection stopped (before finding the document's root tag).return VALIDATION_XSD;
}
  • 1、获取指定的验证模式:这里首先是通过 getValidationMode() 先获取指定的验证模式,没有进行显式配置时,返回的验证模式是默认的 VALIDATION_AUTO 。开发者可以通过以下方法设置和获取指定的验证模式:
/*** Set the validation mode to use. Defaults to {@link #VALIDATION_AUTO}.* <p>Note that this only activates or deactivates validation itself.* If you are switching validation off for schema files, you might need to* activate schema namespace support explicitly: see {@link #setNamespaceAware}.* <p>设置验证模式*/
public void setValidationMode(int validationMode) {this.validationMode = validationMode;
}/*** Return the validation mode to use.*/
public int getValidationMode() {return this.validationMode;
}
  • 2、自动检测验证模式:从代码可以看到,默认的是 VALIDATION_AUTO, 因此需要进行验证模式的自动检测(根据文档的内部声明来获取):detectValidationMode(Resource resource)
/*** Detect which kind of validation to perform on the XML file identified* by the supplied {@link Resource}. If the file has a {@code DOCTYPE}* definition then DTD validation is used otherwise XSD validation is assumed.* <p>Override this method if you would like to customize resolution* of the {@link #VALIDATION_AUTO} mode.* <p>检测 Resource 资源对应的 XML 文件需要执行的验证模式。* 如果 XML 有 DOCTYPE 声明,则使用 DTD,否则使用 XSD* <p>如果需要定制个性化的检测策略,得重写这个方法。*/
protected int detectValidationMode(Resource resource) {// 资源已被打开,抛出 BeanDefinitionStoreException 异常if (resource.isOpen()) {throw new BeanDefinitionStoreException("Passed-in Resource [" + resource + "] contains an open stream: " +"cannot determine validation mode automatically. Either pass in a Resource " +"that is able to create fresh streams, or explicitly specify the validationMode " +"on your XmlBeanDefinitionReader instance.");}// 打开输入流InputStream inputStream;try {inputStream = resource.getInputStream();}catch (IOException ex) {throw new BeanDefinitionStoreException("Unable to determine validation mode for [" + resource + "]: cannot open InputStream. " +"Did you attempt to load directly from a SAX InputSource without specifying the " +"validationMode on your XmlBeanDefinitionReader instance?", ex);}try {// 获取 XML 文件相应的验证模式return this.validationModeDetector.detectValidationMode(inputStream);}catch (IOException ex) {throw new BeanDefinitionStoreException("Unable to determine validation mode for [" +resource + "]: an error occurred whilst reading from the InputStream.", ex);}
}

  detectValidationMode 中将实际的自动检测验证模式的工作委托给了处理类 org.springframework.util.xml.XmlValidationModeDetector,调用了 detectValidationMode(InputStream inputStream) 方法。

  • 3、最终没有找到对应的验证模式时,则使用 XSD 作为默认的验证模式

4、XmlValidationModeDetector.detectValidationMode(InputStream inputStream)

  org.springframework.util.xml.XmlValidationModeDetector:XML 验证模式检测器

/*** Detect the validation mode for the XML document in the supplied {@link InputStream}.* Note that the supplied {@link InputStream} is closed by this method before returning.* <p>通过提供的 XML 文档对应的 InputStream 来检测验证模式* @param inputStream the InputStream to parse* @throws IOException in case of I/O failure* @see #VALIDATION_DTD* @see #VALIDATION_XSD*/
public int detectValidationMode(InputStream inputStream) throws IOException {// Peek into the file to look for DOCTYPE.BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));try {boolean isDtdValidated = false;String content;// 1、逐行读取 XML 文件读的内容while ((content = reader.readLine()) != null) {// content = consumeCommentTokens(content);// 如果读取的行是空或者是注释,则略过if (this.inComment || !StringUtils.hasText(content)) {continue;}// 2、检查是否包含关键字 "DOCTYPE" ,是的话就是 DTD 验证模式if (hasDoctype(content)) {isDtdValidated = true;break;}// 3、读取到 < 开始符号,验证模式一定会在开始符号之前if (hasOpeningTag(content)) {// End of meaningful data...break;}}return (isDtdValidated ? VALIDATION_DTD : VALIDATION_XSD);}catch (CharConversionException ex) {// 4、解析异常,还是返回 VALIDATION_AUTO 模式,由调用者决策// Choked on some character encoding...// Leave the decision up to the caller.return VALIDATION_AUTO;}finally {reader.close();}
}
  • 1、逐行读取 XML 文件读的内容,然后下一步就是根据读取的内容来判断
  • 2、调用 hasDoctype(String content),检查是否包含关键字 "DOCTYPE" ,是的话就是 DTD 验证模式:
/*** The token in a XML document that declares the DTD to use for validation* and thus that DTD validation is being used.*/
private static final String DOCTYPE = "DOCTYPE";/*** Does the content contain the DTD DOCTYPE declaration?*/
private boolean hasDoctype(String content) {return content.contains(DOCTYPE);
}
  • 3、调用 hasOpeningTag(String content) 方法,判断如果这一行包含 < ,并且 < 紧跟着的是字母,则为 XSD 验证模式:
private boolean hasOpeningTag(String content) {if (this.inComment) {return false;}int openTagIndex = content.indexOf('<');// < 存在 且 < 后面还有内容return (openTagIndex > -1 && (content.length() > openTagIndex + 1) &&Character.isLetter(content.charAt(openTagIndex + 1)));    // < 后面的内容是字母
}
  • 4、解析异常,还是返回 VALIDATION_AUTO 模式,由调用者决策。
  • 关于获取 consumeCommentTokens(String line) ,这个是将给定的字符串去掉前导和后导的注释,然后返回剩下的内容,代码如下,可自行研究:
/*** Consume all leading and trailing comments in the given String and return* the remaining content, which may be empty since the supplied content might* be all comment data.* <p>去掉给定字符串的前导和后导注释,并返回剩余的内容。结果可能是空,例如全是注释的情况下。*/
@Nullable
private String consumeCommentTokens(String line) {int indexOfStartComment = line.indexOf(START_COMMENT);if (indexOfStartComment == -1 && !line.contains(END_COMMENT)) {return line;}String result = "";String currLine = line;if (indexOfStartComment >= 0) {result = line.substring(0, indexOfStartComment);currLine = line.substring(indexOfStartComment);}while ((currLine = consume(currLine)) != null) {if (!this.inComment && !currLine.trim().startsWith(START_COMMENT)) {return result + currLine;}}return null;
}/*** Consume the next comment token, update the "inComment" flag* and return the remaining content.*/
@Nullable
private String consume(String line) {int index = (this.inComment ? endComment(line) : startComment(line));return (index == -1 ? null : line.substring(index));
}/*** Try to consume the {@link #START_COMMENT} token.* @see #commentToken(String, String, boolean)*/
private int startComment(String line) {return commentToken(line, START_COMMENT, true);
}private int endComment(String line) {return commentToken(line, END_COMMENT, false);
}
这部分可以参考:
  • spring源码(六)–XmlValidationModeDetector(获取xml文档校验模式)
  • XmlValidationModeDetector

5、总结

  本文主要介绍如何获取 XML 资源文件的验证模式,简单点总结就是:XML 文档中有关键字 "DOCTYPE" 声明的,就是用 DTD 验证模式,其他情况下基本使用 XSD 验证模式。

6、参考

  • spring 官方文档 5.2.3.RELEASE:https://docs.spring.io/spring-framework/docs/5.2.3.RELEASE/spring-framework-reference/core.html
  • Spring源码深度解析(第2版),郝佳,P33-P37,2.6 获取XML的验证模式
  • 死磕Spring系列:死磕 Spring or 【死磕 Spring】----- IOC 之 获取验证模型
  • 芋道源码:http://svip.iocoder.cn/Spring/IoC-Validation-Mode-For-Resource/(死磕 Spring系列的略微修改)
  • 相关注释可参考笔者 github 链接:https://github.com/wpbxin/spring-framework
  • 相关的 UML 图都可以子模块的 diagram 目录下查找

(PS: 这篇写得比较详细也好理解,很赞!)

Spring源码学习1.4 获取XML的验证模式相关推荐

  1. Spring源码学习笔记:经典设计模式之代理模式

    1.博客内容均出自于咕泡学院架构师第三期 2.架构师系列内容:架构师学习笔记(持续更新) 0.代理模式(Proxy Pattern) 指为其他对象提供一种代理,以控制对这个对象的访问.代理对象在客户端 ...

  2. Spring源码学习笔记:经典设计模式之策略模式

    1.博客内容均出自于咕泡学院架构师第三期 2.架构师系列内容:架构师学习笔记(持续更新) 0.策略模式(Strategy pattern) 指定义了算法家族,分别封装起来,让它们之间可以互相替换,此模 ...

  3. spring源码学习之路---深入AOP(终)

    作者:zuoxiaolong8810(左潇龙),转载请注明出处,特别说明:本博文来自博主原博客,为保证新博客中博文的完整性,特复制到此留存,如需转载请注明新博客地址即可. 上一章和各位一起看了一下sp ...

  4. spring源码学习之整合Mybatis原理分析

    本文主要解析spring是如何与mybatis进行整合,整合的过程中需要哪些组件的支持.以前面提到过的配置例子<spring源码学习之aop事物标签解析> 整合的过程中需要使用以下这个依赖 ...

  5. Spring源码 - 从缓存中获取单例Bean

    # Spring源码 - 从缓存中获取单例Bean Spring版本:Spring 5.3.13-release # 1.从缓存中获取单例Bean 单实例Bean在Spring的同一个容器中只会创建一 ...

  6. Spring源码之getBean(获取 bean)方法(二)解读

    目录 前言 `spring` 初始化 `bean` 过程 进入类 `ClassPathXmlApplicationContext` 的构造器 `AbstractApplicationContext` ...

  7. Spring源码学习(四) | @Configuration的cglib动态代理

    文章目录 前言 例子 @Configuration :full or lite 设置 full or lite Cglib生成代理类AppConfig Where is it generated Ho ...

  8. Spring源码学习的初步体会

    Spring源码学习的初步体会: 深入学习和巩固java的基础知识,其中的java知识范围全部,可以边研究源码边巩固复习基础知识 体会其中用到的设计思想:其中包含的设计原则和设计模式. 加深对spri ...

  9. 【Spring源码学习】Spring Bean的销毁

    [Spring源码学习]Spring Bean的销毁 一.注册bean销毁的类 1.registerDisposableBeanIfNecessary() 2.DisposableBeanAdapte ...

最新文章

  1. TortoiseSVN使用方法
  2. 51nod1600-Simple KMP【SAM,树链剖分】
  3. spring 计划任务
  4. jquery获取父级元素和子级元素
  5. ORACLE语句两表相减,Oracle中两个date相减
  6. java vk减号_Vue入门经常使用指令
  7. win10系统的快捷键
  8. 厉害,96秒100亿,阿里双十一到底做了什么杠过亿级流量??
  9. java升级后nc不能用_用友NC系统使用过程中常见问题和解决方法2017
  10. 计算机设备耗材管理系统,实验室耗材管理方法、系统、计算机设备和存储介质与流程...
  11. python 3d游戏引擎哪个好_五大开源游戏引擎介绍
  12. 深海泰坦x86_八代标压,深海泰坦X8Ti深度评测
  13. cad卸载不干净_流氓软件卸不干净?这6款超强软件卸载神器专治各种流氓软件...
  14. 点灯科技Arduino开发环境搭建
  15. 使用Galen进行Responsive Web测试四部曲
  16. ABAP-接口-tcode:SPROXY-tcode:SXI_MONITOR
  17. RobotFramework学习笔记二:遇到Frame框架
  18. 水平导航栏和垂直导航栏
  19. 习题4-3 UVA220黑白棋(WA)
  20. 聊聊我在广州上班打假和反打假的那点事

热门文章

  1. 普中科技51纯代码(流水灯)
  2. virture box上的centos虚拟机,在宿主机ip变化后的修正措施
  3. 校园无线广播系统解决方案
  4. 2021年5月份的月度总结
  5. 怎么使用口袋迷你U盘PE制作工具的ISO模式制作U盘系统
  6. 微信小程序组件所在页面的生命周期
  7. cf不能全屏win7的解决方法_Win10运行DNF全屏后黑屏怎么办|DNF全屏后黑屏解决方法...
  8. 网工知识角:网络工程师能力测试题
  9. texworks注释掉多行latex代码
  10. 分布式事务框架 seata