前言
好久沒有寫博客了,換了工作,才發(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)行解析,看看其是否拋出異常。

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)行上面的程序:

我們會(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);
}
}

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

好了,既然文檔上都這么說了,那么我們就只能啟用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);
}
}

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

下面我們?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);
}
}


可以看到,上面的錯(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é)果:

我們終于看到了"熟悉的錯(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中在外部看來,是通過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)方式,都需要將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è)置完成之后,后面的處理就是完成對Bean定義的加載了。

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