在Java中如何啟用Schema對XML文檔校驗(yàn)

前言

好久沒有寫博客了,換了工作,才發(fā)現(xiàn)入坑(即使大公司也想選擇自己喜歡且合適的部門啊,不是BAT就一定牛逼的)。。。??,不扯了,生活還是要繼續(xù),技術(shù)還是要研究!平時(shí)我們在編寫Spring配置的時(shí)候,一旦在配置上有錯(cuò)誤,在應(yīng)用啟動(dòng)的時(shí)候,就會(huì)拋出校驗(yàn)錯(cuò)誤的異常。最近在研究Spring源碼時(shí),發(fā)現(xiàn)其底層在處理XML元素時(shí),是對解析器做了特殊的配置,才實(shí)現(xiàn)了Schema對XML文檔的校驗(yàn)功能(在默認(rèn)情況下并不支持),帶著好奇心就對其進(jìn)行研究了一下,并且為后續(xù)的源碼分析做一個(gè)基礎(chǔ)和鋪墊。

先來看一個(gè)簡單地例子

PS:Spring底層對于XML的解析使用的Java本身自帶的JAXP來進(jìn)行處理,并沒有使用Dom4j等框架來進(jìn)行處理,至于為什么沒有像Hibernate那樣使用Dom4j來完成配置文件的解釋,我個(gè)人認(rèn)為是:XML的解析工作本身并不是一件復(fù)雜的事情,它就是個(gè)體力活,因此Spring的設(shè)計(jì)者們在設(shè)計(jì)的時(shí)候應(yīng)該是考慮到在處理這些工作的時(shí)候沒有必要再去引入一些第三方的依賴來進(jìn)行處理,因此我們這里的例子也是基于JAXP來說明的。

下面是一份不滿足Schema約束的文檔,我們將使用JAXP來對其進(jìn)行解析,看看其是否拋出異常。


不滿足Schema約束的文檔.png
package com.panlingxiao.spring.ioc.xml;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

/**
 * Created by panlingxiao on 2017/6/5.
 * 使用JAXP解析一份無效的XML文檔(即滿足相應(yīng)DTD和Schema定義的元素規(guī)則),
 */
public class ValidateXmlTest {

    public static void main(String[] args) throws Exception{
       DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
        DocumentBuilder documentBuilder = builderFactory.newDocumentBuilder();
        //解析文檔
        Document document = documentBuilder.parse(ValidateXmlTest.class.getClassLoader().getResourceAsStream("validation/error.xml"));
        System.out.println(document);
    }
}

運(yùn)行上面的程序:

運(yùn)行結(jié)果1.png

我們會(huì)發(fā)現(xiàn),上面的運(yùn)行結(jié)果壓根沒有拋出異常,從而我們可以得到結(jié)論:在默認(rèn)的情況下,使用Jaxp的DOM解析并不會(huì)使用Shema完成對XML文檔的校驗(yàn)。

啟用Schema對XML文檔校驗(yàn)

如果在使用JAXP解析XML文檔時(shí),希望能夠?qū)崿F(xiàn)通過Schema對其進(jìn)行校驗(yàn)的話,我們只需完成以下的幾個(gè)步驟即可:

  • 設(shè)置DocumentBuilderFactory,讓底層的解析器啟用校驗(yàn)功能
  • 設(shè)置DocumentBuilderFactory屬性,指定使用哪一個(gè)Schema完成對文檔的校驗(yàn)(默認(rèn)都是W3C規(guī)范所提供的Schema)
  • 設(shè)置DocumentBuilderFactory,讓底層的解析器提供對命名空間的支持,因?yàn)槟J(rèn)情況下其只支持DTD,解析器并不支持命名空間,如果解析器不支持命名空間,那么其Schema校驗(yàn)依然無效(這點(diǎn)需要特別注意)
  • 為DocumentBuilder設(shè)置一個(gè)ErrorHandler,如果不設(shè)置,則會(huì)使用默認(rèn)的ErrorHandler,它只會(huì)給出警告信息,并不會(huì)拋出異常,因此那樣對于我們來說就沒啥意義了

下面就讓我們驗(yàn)證上面的步驟,這里我并不準(zhǔn)備直接給出一個(gè)最終的結(jié)果,而是通過每一個(gè)步驟,來驗(yàn)證上面的結(jié)論,這樣更能清楚的幫助我們理解它們的作用和意義。

設(shè)置啟用解析器進(jìn)行文檔校驗(yàn):

package com.panlingxiao.spring.ioc.xml;

import org.w3c.dom.Document;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

/**
 * Created by panlingxiao on 2017/6/5.
 * 使用JAXP解析一份無效的XML文檔(即滿足相應(yīng)DTD和Schema定義的元素規(guī)則),
 */
public class ValidateXmlTest {

    public static void main(String[] args) throws Exception{
        DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
        //設(shè)置解析器在解析文檔的時(shí)候校驗(yàn)文檔
        builderFactory.setValidating(true);

        DocumentBuilder documentBuilder = builderFactory.newDocumentBuilder();
        //解析文檔
        Document document = documentBuilder.parse(ValidateXmlTest.class.getClassLoader().getResourceAsStream("validation/error.xml"));
        System.out.println(document);
    }
}
設(shè)置啟用解析器進(jìn)行文檔校驗(yàn)---運(yùn)行結(jié)果.png

通過上面的警告,我們可以看到兩個(gè)問題:第一是在校驗(yàn)的過程中發(fā)生了錯(cuò)誤,但是并沒有拋出異常,第二是說在校驗(yàn)的過程中沒有找到DOCTYPE,即沒有找到對應(yīng)DTD文檔,這也說明了解析器在默認(rèn)情況下并不支持XSD的校驗(yàn)。

不支持Schema的原因分析.png

好了,既然文檔上都這么說了,那么我們就只能啟用XSD的校驗(yàn)!

使用XSD校驗(yàn)

package com.panlingxiao.spring.ioc.xml;

import org.w3c.dom.Document;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

/**
 * Created by panlingxiao on 2017/6/5.
 * 使用JAXP解析一份無效的XML文檔(即滿足相應(yīng)DTD和Schema定義的元素規(guī)則),
 */
public class ValidateXmlTest {
    //定義兩個(gè)常量,注意:這兩個(gè)常量是固定值
    static final String JAXP_SCHEMA_LANGUAGE = 
"http://java.sun.com/xml/jaxp/properties/schemaLanguage";
          
    static final String W3C_XML_SCHEMA ="http://www.w3.org/2001/XMLSchema";

    public static void main(String[] args) throws Exception{
        DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
        //設(shè)置解析器在解析文檔的時(shí)候校驗(yàn)文檔
        builderFactory.setValidating(true);
        //通過指定factory的屬性,確定使用Schema進(jìn)行校驗(yàn)
        builderFactory. setAttribute(JAXP_SCHEMA_LANGUAGE,W3C_XML_SCHEMA);

        DocumentBuilder documentBuilder = builderFactory.newDocumentBuilder();

        //解析文檔
        Document document = documentBuilder.parse(ValidateXmlTest.class.getClassLoader().getResourceAsStream("validation/error.xml"));
        System.out.println(document);
    }
}
啟用XSD的校驗(yàn)的結(jié)果.png

可以看到,這次的錯(cuò)誤信息和上面所產(chǎn)生的信息完全不同,而且像cvs-xxx這種錯(cuò)誤,我們平時(shí)在編寫Spring配置的時(shí)候也經(jīng)常遇到,其本質(zhì)就是因?yàn)槊臻g所導(dǎo)致的。

命名空間造成的錯(cuò)誤.png

下面我們?yōu)榻馕銎骷由蠈γ臻g的支持:

package com.panlingxiao.spring.ioc.xml;

import org.w3c.dom.Document;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

/**
 * Created by panlingxiao on 2017/6/5.
 * 使用JAXP解析一份無效的XML文檔(即滿足相應(yīng)DTD和Schema定義的元素規(guī)則),
 */
public class ValidateXmlTest {

    //定義兩個(gè)常量,注意:這兩個(gè)常量是固定值
    static final String JAXP_SCHEMA_LANGUAGE =
            "http://java.sun.com/xml/jaxp/properties/schemaLanguage";
    static final String W3C_XML_SCHEMA =
            "http://www.w3.org/2001/XMLSchema";

    public static void main(String[] args) throws Exception{
        DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
        //設(shè)置解析器在解析文檔的時(shí)候校驗(yàn)文檔
        builderFactory.setValidating(true);
        //通過指定factory的屬性,確定使用Schema進(jìn)行校驗(yàn)
        builderFactory. setAttribute(JAXP_SCHEMA_LANGUAGE,W3C_XML_SCHEMA);
        //讓解析器支持命名空間
        builderFactory.setNamespaceAware(true);

        DocumentBuilder documentBuilder = builderFactory.newDocumentBuilder();
        //解析文檔
        Document document = documentBuilder.parse(ValidateXmlTest.class.getClassLoader().getResourceAsStream("validation/error.xml"));
        System.out.println(document);
    }
}
解析器支持命名空間后的結(jié)果.png
直接使用IDE的validate校驗(yàn)功能.png

可以看到,上面的錯(cuò)誤信息是一樣的,只是我們的代碼中并沒有將錯(cuò)誤以異常的形式拋出,而只是以警告的信息輸出而已,我們只需加上一個(gè)ErrorHandler,自己手動(dòng)地將異常輸出即可。

package com.panlingxiao.spring.ioc.xml;

import org.w3c.dom.Document;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

/**
 * Created by panlingxiao on 2017/6/5.
 * 使用JAXP解析一份無效的XML文檔(即滿足相應(yīng)DTD和Schema定義的元素規(guī)則),
 */
public class ValidateXmlTest {

    //定義兩個(gè)常量,注意:這兩個(gè)常量是固定值
    static final String JAXP_SCHEMA_LANGUAGE =
            "http://java.sun.com/xml/jaxp/properties/schemaLanguage";
    static final String W3C_XML_SCHEMA =
            "http://www.w3.org/2001/XMLSchema";

    public static void main(String[] args) throws Exception{
        DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
        //設(shè)置解析器在解析文檔的時(shí)候校驗(yàn)文檔
        builderFactory.setValidating(true);
        //通過指定factory的屬性,確定使用Schema進(jìn)行校驗(yàn)
        builderFactory. setAttribute(JAXP_SCHEMA_LANGUAGE,W3C_XML_SCHEMA);
        //讓解析器支持命名空間
        builderFactory.setNamespaceAware(true);

        DocumentBuilder documentBuilder = builderFactory.newDocumentBuilder();
        //添加ErrorHandler,將解析的異常手動(dòng)拋出
        documentBuilder.setErrorHandler(new ErrorHandler() {
            @Override
            public void warning(SAXParseException exception) throws SAXException {
                System.out.println("warning");
                throw  exception;
            }

            @Override
            public void error(SAXParseException exception) throws SAXException {
                System.out.println("error");
                throw  exception;
            }

            @Override
            public void fatalError(SAXParseException exception) throws SAXException {
                System.out.println("fatal");
                throw  exception;
            }
        });

        //解析文檔
        Document document = documentBuilder.parse(ValidateXmlTest.class.getClassLoader().getResourceAsStream("validation/error.xml"));
        System.out.println(document);
    }
}

運(yùn)行結(jié)果:


加上ErrorHandler后的運(yùn)行結(jié)果.png

我們終于看到了"熟悉的錯(cuò)誤",通過以上的配置,我們即可完成在解析XML文檔時(shí),同時(shí)使用Schema來對文檔本身是否有效進(jìn)行校驗(yàn)。這里需要說明一下的是,兩種校驗(yàn)方式是無法共存的,默認(rèn)使用的是DTD校驗(yàn),但是我們一旦設(shè)置了使用Schema校驗(yàn),那么即使是一份使用DTD約束的有效文檔,也校驗(yàn)時(shí)也會(huì)出錯(cuò)。

Spring中的處理方式

分析完上面的內(nèi)容,我們知道了自己如何去使用Schema對XML文檔校驗(yàn)。下面我們來看一下Spring中處理方式。由于Spring中內(nèi)容較多,我們不再從頭開始分析起,我們會(huì)直接定位到我們關(guān)注的部分進(jìn)行查看。

Spring中的處理方式.png

這里需要說明一下的是,Spring中在外部看來,是通過XMLBeanDefinitionReader來完成Bean定義的加載、解析、以及Bean定義的注冊,但是XMLBeanDefinitionReader其底層維護(hù)了多個(gè)組件來完成上面的每一件事情,這很好地體現(xiàn)了單一職責(zé)的設(shè)計(jì)。而DefaultDocumentLoader這個(gè)組件,就是完成Bean定義的加載功能。

上面的源碼非常簡單:
(1). 它首先創(chuàng)建出DocumentBuilderFactory,并且設(shè)置namespaceAware的值,但是這里的值是由外部傳入的,那么它到底是true還是false呢?它的默認(rèn)設(shè)置是false,其原因是因?yàn)樵O(shè)置namespaceAware為true的情況只有在使用Schema的校驗(yàn)情況下進(jìn)行處理,而如果是DTD校驗(yàn),實(shí)際上是沒有必要的。然而由于Spring早期的校驗(yàn)方式是基于DTD的,到了2.5之后才出現(xiàn)了XSD的校驗(yàn)方式。為了兼容多種情況的存在,因此它這里一開始將namespaceAware設(shè)置為false,一旦發(fā)現(xiàn)是XSD校驗(yàn)時(shí),再把它重新設(shè)置為true即可。

(2).判斷當(dāng)前的校驗(yàn)?zāi)J剑磁袛嗍荄TD校驗(yàn)還是XSD校驗(yàn)。在Spring中,是通過一個(gè)int值來區(qū)分校驗(yàn)的模式。

校驗(yàn)?zāi)J?png

不管使用哪種校驗(yàn)方式,都需要將DocumentBuilderFactory的validating設(shè)置為true。如果發(fā)現(xiàn)使用的是XSD校驗(yàn),則強(qiáng)制地設(shè)置讓解析器去支持namespace。并且設(shè)置使用標(biāo)準(zhǔn)的W3C所提供的XSD進(jìn)行校驗(yàn),后面的設(shè)置與我們上面的實(shí)例是一樣的。

設(shè)置Factory的屬性.png

在設(shè)置完成之后,后面的處理就是完成對Bean定義的加載了。

加載Bean定義.png

至此,我們就已經(jīng)分析完成所有如何使用Schema校驗(yàn)的全部內(nèi)容,如果對您有所幫助,請為我點(diǎn)個(gè)??,謝謝~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,688評論 19 139
  • 1. XML簡介 以下內(nèi)容來自于http://www.w3school.com.cn/xml 基本知識 XML 和...
    WebSSO閱讀 2,092評論 1 7
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 47,285評論 6 342
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法,類相關(guān)的語法,內(nèi)部類的語法,繼承相關(guān)的語法,異常的語法,線程的語...
    子非魚_t_閱讀 34,823評論 18 399
  • Idea SpringMVC+Spring+MyBatis+Maven整合 創(chuàng)建項(xiàng)目 File-New Proje...
    mingli_jianshu1閱讀 1,770評論 2 15

友情鏈接更多精彩內(nèi)容