zoukankan      html  css  js  c++  java
  • Spring5源码分析(008)——IoC篇之加载BeanDefinition:获取XML的验证模式

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


      上一篇《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 的验证模式

      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);
    }

    这部分可以参考:

    5、总结

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

    6、参考

  • 相关阅读:
    Leetcode 238. Product of Array Except Self
    Leetcode 103. Binary Tree Zigzag Level Order Traversal
    Leetcode 290. Word Pattern
    Leetcode 205. Isomorphic Strings
    Leetcode 107. Binary Tree Level Order Traversal II
    Leetcode 102. Binary Tree Level Order Traversal
    三目运算符
    简单判断案例— 分支结构的应用
    用switch判断月份的练习
    java基本打印练习《我行我素购物系统》
  • 原文地址:https://www.cnblogs.com/wpbxin/p/13207581.html
Copyright © 2011-2022 走看看