前言
好久沒(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)行解析,看看其是否拋出異常辫诅。
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)行上面的程序:
我們會(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);
}
}
通過(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)。
好了楣黍,既然文檔上都這么說(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);
}
}
可以看到哩治,這次的錯(cuò)誤信息和上面所產(chǎn)生的信息完全不同秃踩,而且像cvs-xxx
這種錯(cuò)誤,我們平時(shí)在編寫Spring配置的時(shí)候也經(jīng)常遇到锚扎,其本質(zhì)就是因?yàn)槊臻g所導(dǎo)致的吞瞪。
下面我們?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);
}
}
可以看到,上面的錯(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é)果:
我們終于看到了"熟悉的錯(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)行查看钻心。
這里需要說(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)方式,都需要將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è)置完成之后,后面的處理就是完成對(duì)Bean定義的加載了搀暑。
至此沥阳,我們就已經(jīng)分析完成所有如何使用Schema校驗(yàn)的全部?jī)?nèi)容,如果對(duì)您有所幫助自点,請(qǐng)為我點(diǎn)個(gè)??桐罕,謝謝~