08-Spring初級容器初始化:獲取聲明文件和校驗(yàn)類型

開篇
? ? ? ?上一節(jié),我們了解了Spring對xml的校驗(yàn)有兩種校驗(yàn)方式,分別是DTD和XSD,并且,這兩種校驗(yàn)方式都有對應(yīng)的解析器來獲取jar包中的聲明文件。
? ? ? ?這一節(jié),我們就來看下這兩個解析器,也就是BeansDtdResolver和PluggableSchemaResolver分別是如何從jar包中獲取相應(yīng)的聲明文件的,只要有以下幾個部分:
? ? ? ?1.首先我們會到BeansDtdResolver類中,看下它是如何解析獲取DTD的聲明文件的
? ? ? ?2.再到PluggableSchemaResolver類中,看下相應(yīng)的XSD聲明文件是如何獲取的
? ? ? ?3.最后來看下Spring是如何判定當(dāng)前xml文件,它到底應(yīng)該用哪種方式來校驗(yàn)?zāi)兀?/span>
如何獲取dtd聲明文件呢?
? ? ? ?我們先到BeansDtdResolver這個類中,看下DTD聲明文件是如何獲取的:

? ? ? ?進(jìn)入到BeansDtdResolver類中,簡單尋找了一下果然就發(fā)現(xiàn)了一個方法resolveEntity,很顯然它就是解析并獲取DTD文件的方法,可以看到方法resolveEntity需要傳入兩個參數(shù),分別是publicId和systemId。
? ? ? ?其實(shí)這兩個參數(shù)都是從xml文件獲取的,我們來先看下剛才包含dtd聲明的xml文件:

? ? ? ?Spring在解析xml文件時,會從xml文件中獲取到這兩個參數(shù)的值,分別如下:
publicId:-//SPRING//DTD BEAN//EN
systemId:http://www.springframework.org/dtd/spring-beans.dtd
? ? ? ?了解完這個之后,我們再回到剛才解析dtd文件的方法resolveEntity:

? ? ? ?可以看到在resolveEntity方法中,當(dāng)判斷systemId非空并且是以.dtd為后綴時,就會默認(rèn)拼接并獲取dtd聲明文件的名稱dtdFile,也就是spring-beans.dtd。
? ? ? ?然后通過ClassPathResource類,從classpath路徑下加載spring-beans.dtd聲明文件,最后將publicId、systemId和ClassPathResource中的輸入流,都封裝成InputSource返回。
? ? ? ?現(xiàn)在基本已經(jīng)弄清楚了,我們可以到的spring-beans的源碼項(xiàng)目的classpath路徑下看下,那具體到classpath下的哪個路徑下尋找呢?
? ? ? ?不知道大家剛才有沒有注意到,在創(chuàng)建ClassPathResource時,還通過方法getClass傳入了當(dāng)前的類也就是BeansDtdResolver,這樣的話ClassPathResource就會從classpath路徑下,到和BeansDtdResolver類相同包名的路徑下,尋找相應(yīng)的spring-beans.dtd文件,我們來看下:

? ? ? ?果然,因?yàn)锽eansDtdResolver的包名為org.springframework.beans.factory.xml,相應(yīng)的,我們也在classpath下的相應(yīng)包中找到了spring-beans.dtd聲明文件。
如何獲取xsd聲明文件呢?
? ? ? ?DTD聲明文件如何獲取我們已經(jīng)清楚了,現(xiàn)在我們再到PluggableSchemaResolver類中,看下如何獲取XSD聲明文件的:

? ? ? ?可以看到,在PluggableSchemaResolver類中也存在相應(yīng)的resolveEntity方法,而且也需要傳遞參數(shù)publicId和systemId。
? ? ? ?我們再來看下XSD校驗(yàn)類型的xml文件:

? ? ? ?Spring在解析XSD校驗(yàn)類型的xml文件時,同樣也會獲取到publicId和systemId這兩個參數(shù)的值:
publicId:null
systemId:http://www.springframework.org/schema/beans/spring-beans.xsd
? ? ? ?通過對比DTD的參數(shù)發(fā)現(xiàn),publicId這個參數(shù)的值只有DTD校驗(yàn)類型才有的,而參數(shù)systemId其實(shí)就是聲明文件的下載地址,DTD和XSD都有相應(yīng)的地址。
? ? ? ?獲取到參數(shù)之后,我們回過頭來看下方法resolveEntity是如何獲取XSD聲明文件的:

? ? ? ?可以看到,首先參數(shù)systemId肯定非空,此時就會調(diào)用getSchemaMappings方法,通過傳入?yún)?shù)systemId獲取到資源的位置resourceLocation。
? ? ? ?那getSchemaMapping方法是干什么的呢?我們先到getSchemaMapping方法中看下:

? ? ? ?可以看到,在getSchemaMappings方法中,成員變量schemaMappings一開始肯定為空,所以,我們可以看到通過PropertiesLoaderUtils的loadAllProperties方法,會加載schemaMappingsLocation中的所有屬性。
? ? ? ?那schemaMappingsLocation又是什么呢,我們繼續(xù)來看下:

? ? ? ?可以看到,默認(rèn)情況下PluggableSchemaResolver類在構(gòu)造方法中,會給成員變量schemaMappingsLocation賦值為“META-INF/spring.schemas”。
? ? ? ?這就好辦了,也就是說PropertiesLoaderUtils的loadAllProperties方法就是加載spring.schemas中的所有屬性,那具體加載什么屬性呢,我們到spring-beans項(xiàng)目中的META-INF目錄中看一下:

? ? ? ?果然,我們在META-INF目錄中找到了spring.schemas文件,我們看下spring.schemas文件中是什么:

? ? ? ?看到這里我們算是明白了,原來在spring.schemas文件中,存放的就是以systemId為key,以XSD聲明文件在項(xiàng)目中的包名路徑作為value,所以我們通過systemId:
http://www.springframework.org/schema/beans/spring-beans.xsd
? ? ? ?可以獲取到對應(yīng)的value值:
org/springframework/beans/factory/xml/spring-beans.xsd
? ? ? ?原來相比于DTD聲明文件的獲取方式,XSD聲明文件的獲取,還要根據(jù)具體的配置配置來尋找。
? ? ? ?了解完這些之后,我們再回到getSchemaMappings方法:

? ? ? ?也就是說,最后會把spring.schemas中的所有屬性封裝成一個Map然后返回去,接下來我們可以猜到的就是會通過參數(shù)systemId的值,獲取XSD文件在項(xiàng)目中的路徑去獲取XSD文件,這個我們剛才都看過的,所以接下來無非也就是這樣。
? ? ? ?我們再回到resolveEntity方法:

? ? ? ?可以看到,通過systemId到schemaMappings中獲取相應(yīng)的resourceLocation,剛才我們看到了systemId對應(yīng)的value值就是org/springframework/beans/factory/xml/spring-beans.xsd,也就是spring-beans.xsd聲明文件在項(xiàng)目中的位置。
? ? ? ?我們可以看到接下來會通過ClassPathResource,到classpath路徑下的resourceLocation位置加載資源,和DTD類似也是將結(jié)果封裝為InputSource返回。
? ? ? ?那在classpath路徑下,我們能不能找到相應(yīng)的XSD聲明文件呢,我們再來看下:

? ? ? ?果然,在classpath路徑下的包名路徑中,我們也找到相應(yīng)的XSD聲明文件。
如何獲取xml的校驗(yàn)類型呢?
? ? ? ?分析到這里,DTD和XSD聲明文件也都通過相應(yīng)的EntityResolver實(shí)現(xiàn)類,都已經(jīng)找到了,最后還有一個問題,那就是Spring是如何知道當(dāng)前的解析的xml文件,是DTD類型的還是XSD類型的呢?
? ? ? ?要回答這個問題,我們得要再回到方法doLoadDocument中看下:

? ? ? ?可以看到,在調(diào)用loadDocument方法時不僅調(diào)用getEntityResolver方法,傳入聲明文件的解析器的同時,還調(diào)用getValidationModeForResource方法來獲取當(dāng)前xml文件的校驗(yàn)類型。
? ? ? ?我們到getValidationModeForResource方法中,看下xml文件的校驗(yàn)類型是如何判斷的:

? ? ? ?首先,會通過getValidationMode方法獲取默認(rèn)的校驗(yàn)?zāi)J剑覀兛梢赃M(jìn)去看下:

? ? ? ?可以看到,默認(rèn)的校驗(yàn)類型,就是VALIDATION_AUTO,第一個if分支條件不符。
? ? ? ?接下來,我們就直接來到到detectValidationMode方法中,看下它是如何自動檢測xml校驗(yàn)類型的:

? ? ? ?可以看到,在detectValidationMode方法中,首先獲取Resource資源的輸入流,然后立馬又委托給了XmlValidationModeDetector的validationModeDetector方法去檢測了。
? ? ? ?我們繼續(xù)跟進(jìn)一下validationModeDetector方法:

? ? ? ?可以看到,首先會將輸入流InputStream封裝為一個可以緩沖的字符輸入流,方便讀取InputStream,可以看到方法中,關(guān)鍵在于while循環(huán)中的hasDoctype方法,我們到hasDoctype方法中看下:

? ? ? ?塵埃落定,也就是說只要檢測到xml的內(nèi)容中,包含“DOCTYPE”字符串,就認(rèn)定xml文件的校驗(yàn)方式為DTD,否則就是XSD。
? ? ? ?這樣的話Spring就可以根據(jù)具體的校驗(yàn)類型,分別使用不同的解析器去獲取相應(yīng)的校驗(yàn)文件,xml文件在解析時,至少對xml文件的基本格式和規(guī)范就可以得到保障了。
總結(jié)
? ? ? ?好了,今天的知識點(diǎn),我們就講到這里了,我們來總結(jié)一下吧。
? ? ? ?一張圖來梳理下當(dāng)前的流程:
? ? ? ?這一節(jié),我們主要了解了兩部分的內(nèi)容,一部分是DTD和XSD對應(yīng)的解析器EntityResolver,看下它們是如何從jar包中獲取對應(yīng)的聲明文件的,有了這樣的機(jī)制存在,Spring在校驗(yàn)xml文件時,就不需要從網(wǎng)上臨時下載聲明文件,避免了因?yàn)閿嗑W(wǎng)或網(wǎng)絡(luò)抖動對程序影響。
? ? ? ?另外一點(diǎn),我們也了解了Spring是如何判定xml文件,到底該使用DTD還是XSD校驗(yàn)方式,通過源碼的分析我們知道,其實(shí)就是根據(jù)xml文件中是否存在“DOCTYPE”字符串來判定的,很簡單如果xml中存在字符串“DOCTYPE”就是DTD校驗(yàn)方式,否則就是XSD校驗(yàn)方式。
? ? ? ?接下來,具體如何根據(jù)DTD或XSD的解析器去校驗(yàn)xml文件,那就要交給DOM相關(guān)的API去校驗(yàn)了,關(guān)于DTD和XSD相關(guān)的東西,我們了解到這里也就差不多了。
? ? ? ?接下來,我們就要正式來看下Spring,到底是如何一步步解析Document中的各種標(biāo)簽元素,然后將解析到的信息注入到Spring容器中的,后續(xù)的章節(jié)我們拭目以待。