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

前言

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

先來(lái)看一個(gè)簡(jiǎn)單地例子

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

下面是一份不滿足Schema約束的文檔止潘,我們將使用JAXP來(lái)對(duì)其進(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解析一份無(wú)效的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é)果壓根沒(méi)有拋出異常炕矮,從而我們可以得到結(jié)論:在默認(rèn)的情況下么夫,使用Jaxp的DOM解析并不會(huì)使用Shema完成對(duì)XML文檔的校驗(yàn)。

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

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

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

下面就讓我們驗(yàn)證上面的步驟趾盐,這里我并不準(zhǔn)備直接給出一個(gè)最終的結(jié)果,而是通過(guò)每一個(gè)步驟,來(lái)驗(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解析一份無(wú)效的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

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

不支持Schema的原因分析.png

好了楣黍,既然文檔上都這么說(shuō)了匾灶,那么我們就只能啟用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解析一份無(wú)效的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);
        //通過(guò)指定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)榻馕銎骷由蠈?duì)命名空間的支持:

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解析一份無(wú)效的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);
        //通過(guò)指定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ò)誤信息是一樣的翠勉,只是我們的代碼中并沒(méi)有將錯(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解析一份無(wú)效的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);
        //通過(guò)指定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ò)誤",通過(guò)以上的配置骑脱,我們即可完成在解析XML文檔時(shí)菜枷,同時(shí)使用Schema來(lái)對(duì)文檔本身是否有效進(jìn)行校驗(yàn)。這里需要說(shuō)明一下的是叁丧,兩種校驗(yàn)方式是無(wú)法共存的啤誊,默認(rèn)使用的是DTD校驗(yàn)岳瞭,但是我們一旦設(shè)置了使用Schema校驗(yàn),那么即使是一份使用DTD約束的有效文檔蚊锹,也校驗(yàn)時(shí)也會(huì)出錯(cuò)瞳筏。

Spring中的處理方式

分析完上面的內(nèi)容,我們知道了自己如何去使用Schema對(duì)XML文檔校驗(yàn)牡昆。下面我們來(lái)看一下Spring中處理方式姚炕。由于Spring中內(nèi)容較多,我們不再?gòu)念^開始分析起迁杨,我們會(huì)直接定位到我們關(guān)注的部分進(jìn)行查看钻心。

Spring中的處理方式.png

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

上面的源碼非常簡(jiǎn)單:
(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í)際上是沒(méi)有必要的熄捍。然而由于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中轨域,是通過(guò)一個(gè)int值來(lái)區(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è)置完成之后,后面的處理就是完成對(duì)Bean定義的加載了搀暑。

加載Bean定義.png

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市桂敛,隨后出現(xiàn)的幾起案子功炮,更是在濱河造成了極大的恐慌,老刑警劉巖术唬,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件薪伏,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡粗仓,警方通過(guò)查閱死者的電腦和手機(jī)嫁怀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)借浊,“玉大人塘淑,你說(shuō)我怎么就攤上這事÷旖铮” “怎么了存捺?”我有些...
    開封第一講書人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)曙蒸。 經(jīng)常有香客問(wèn)我捌治,道長(zhǎng),這世上最難降的妖魔是什么逸爵? 我笑而不...
    開封第一講書人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任具滴,我火速辦了婚禮,結(jié)果婚禮上师倔,老公的妹妹穿的比我還像新娘构韵。我一直安慰自己,他們只是感情好趋艘,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開白布疲恢。 她就那樣靜靜地躺著,像睡著了一般瓷胧。 火紅的嫁衣襯著肌膚如雪显拳。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,631評(píng)論 1 305
  • 那天搓萧,我揣著相機(jī)與錄音杂数,去河邊找鬼宛畦。 笑死,一個(gè)胖子當(dāng)著我的面吹牛揍移,可吹牛的內(nèi)容都是我干的次和。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼那伐,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼踏施!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起罕邀,我...
    開封第一講書人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤畅形,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后诉探,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體日熬,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年肾胯,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了碍遍。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡阳液,死狀恐怖怕敬,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情帘皿,我是刑警寧澤东跪,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站鹰溜,受9級(jí)特大地震影響虽填,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜曹动,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一斋日、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧墓陈,春花似錦恶守、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至仔拟,卻和暖如春衫樊,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工科侈, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留载佳,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓臀栈,卻偏偏與公主長(zhǎng)得像刚盈,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子挂脑,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

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