javascript
什么是spring_Spring 源码第三弹!EntityResolver 是个什么鬼?
上篇文章和小伙伴們說了 Spring 源碼中 XML 文件的解析流程,本來可以繼續(xù)往下走看加載核心類了,但是松哥還是希望能夠慢一點,既然要學(xué)就學(xué)懂,在 XML 文件解析的過程中還涉及到一些其他的類和概念,因此我就先用幾篇文章介紹一下這些涉及到的概念或者類,然后我們再繼續(xù)往下看。
本文要和大家介紹的是上篇文章中涉及到的 EntityResolver 類,看看這個類到底是干嘛用的。
本文是 Spring 源碼系列第四篇,閱讀前面文章有助于更好理解本文:
先來回顧下,在 EntityResolver 這個類在上篇文章哪里出現(xiàn)了。
我們在講到 doLoadDocument 方法時,在該方法中調(diào)用 loadDocument 方法時,傳遞的第二個參數(shù)就是一個 EntityResolver 實例,當(dāng)時我們說這個是用來處理文件的驗證方式的,但是到底是怎么處理的,今天我們就來看下。
1.XML 驗證模式
要了解 EntityResolver,就得先來看看 XML 文件驗證模式。
現(xiàn)在我們大多數(shù)情況下可能都是使用 JSON 傳遞數(shù)據(jù),XML 使用較少,可能有的小伙伴對 XML 文件的一些規(guī)則還不太熟悉,我這里稍微說一下。
XML 是指可擴展標(biāo)記語言(eXtensible Markup Language),它是一種標(biāo)記語言,類似 HTML;XML 標(biāo)簽沒有被預(yù)定義,需要用戶自行定義標(biāo)簽,也就是 XML 文件中的節(jié)點都是用戶自定義的。XML 文件從設(shè)計之初就是為了傳輸數(shù)據(jù),而非顯示數(shù)據(jù)。
一般來說,一個 XML 文件由六個部分組成:
- 文檔生命
- 元素
- 屬性
- 注釋
- CDATA 區(qū)
- 處理指令
雖然說 XML 文件本身是沒有預(yù)定義 XML 標(biāo)簽,但是當(dāng) XML 文件作為框架的配置時,對于 XML 標(biāo)簽還是要有一定的約束,否則每個人都按照自己的喜好定義 XML 標(biāo)簽,框架就沒法讀取這樣的 XML 文件了。
在 XML 技術(shù)中,開發(fā)者可以通過一個文檔來約束一個 XML 的文檔中的標(biāo)簽,這個文檔稱之為約束。遵循 XML 語法的 XML 我們稱之為格式良好的 XML,而遵循 XML 約束的 XML 我們稱之為有效的 XML。XML 約束文檔主要定義了在 XML 中允許出現(xiàn)的元素名稱、屬性及元素出現(xiàn)的順序等等。
要想約束 XML 標(biāo)簽,有兩種方式:
DTD(Document Type Definition),全稱為文檔類型定義,一個 DTD 約束文件我們既可以定義在 XML 文件內(nèi)部,也可以定義一個本地文件,也可以引用一個網(wǎng)絡(luò)上的公共的 DTD。
XML Schema 也是一種用于定義和描述 XML 文檔結(jié)構(gòu)與內(nèi)容的模式語言,相比于 DTD,Schema 對于名稱空間的支持更加友好,同時也支持更多的數(shù)據(jù)類型,而且它的約束能力也比較強大,另外還有非常重要的一點是,Schema 文檔本身也是 XML 文檔,而不是像 DTD 一樣使用自成一體的語法。
所以,Schema 目前在 XML 約束這塊更具備優(yōu)勢,也在逐漸替代 DTD。
大家在日常開發(fā)中,這兩種約束可能都見過,但是有的人可能沒注意。我給大家簡單舉一個例子。
早期的 Spring 配置頭部是這樣的(Spring2.x),這就是 DTD 約束:
<?xml ?version="1.0"?encoding="UTF-8"?>??beans?PUBLIC?"-//SPRING//DTD?BEAN?2.0//EN"??"http://www.springframework.org/dtd/spring-beans-2.0.dtd">????????現(xiàn)在大家看到的 Spring 配置頭部一般都是這樣,這就是 Schema 約束:
<?xml ?version="1.0"?encoding="UTF-8"?>schema 約束對命名空間有著很好的支持,命名空間可以防止命名沖突,schema 中的名稱空間和約束文件都是成對出現(xiàn)的。
有了約束,XML 文件中該寫什么不該寫什么就固定下來了,這樣框架才能成功解析出 XML 文件。
但是大家同時也發(fā)現(xiàn)了一個新的問題,無論是 DTD 還是 Schema 約束,給出的約束文件地址都是一個在線地址,這就意味著項目啟動時必須能夠訪問到該在線地址,才能加載到約束文件,如果訪問在線約束文件失敗,那么項目啟動也會失敗。
為了解決這個問題,框架一般都是將約束文件放在本地的,在本地哪里呢?實際上就在你下載的 jar 包里。以 spring-beans 為例,在下載的 jar 包里有如下兩個文件:
spring.handlers 文件內(nèi)容如下:
http://www.springframework.org/schema/c=org.springframework.beans.factory.xml.SimpleConstructorNamespaceHandlerhttp://www.springframework.org/schema/p=org.springframework.beans.factory.xml.SimplePropertyNamespaceHandlerhttp://www.springframework.org/schema/util=org.springframework.beans.factory.xml.UtilNamespaceHandler這其實一個映射配置,每一個名稱空間對應(yīng)的處理類在這里進(jìn)行配置。
spring.schemas 文件內(nèi)容如下(部分):
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd=org/springframework/beans/factory/xml/spring-beans.xsdhttp://www.springframework.org/schema/beans/spring-beans-2.5.xsd=org/springframework/beans/factory/xml/spring-beans.xsdhttp://www.springframework.org/schema/beans/spring-beans-3.0.xsd=org/springframework/beans/factory/xml/spring-beans.xsdhttp://www.springframework.org/schema/beans/spring-beans-3.1.xsd=org/springframework/beans/factory/xml/spring-beans.xsdhttp://www.springframework.org/schema/beans/spring-beans-3.2.xsd=org/springframework/beans/factory/xml/spring-beans.xsdhttp://www.springframework.org/schema/beans/spring-beans-4.0.xsd=org/springframework/beans/factory/xml/spring-beans.xsdhttp://www.springframework.org/schema/beans/spring-beans-4.1.xsd=org/springframework/beans/factory/xml/spring-beans.xsdhttp://www.springframework.org/schema/beans/spring-beans-4.2.xsd=org/springframework/beans/factory/xml/spring-beans.xsdhttp://www.springframework.org/schema/beans/spring-beans-4.3.xsd=org/springframework/beans/factory/xml/spring-beans.xsdhttp://www.springframework.org/schema/beans/spring-beans.xsd=org/springframework/beans/factory/xml/spring-beans.xsd可以看到,各種版本以及沒有版本號的約束文件,都對應(yīng)了同一個文件,就是 org/springframework/beans/factory/xml/spring-beans.xsd,打開這個文件目錄,我們就可以看到約束文件:
所以我們雖然在 Spring 的 XML 配置中看到的約束文件是一個在線地址,實際上約束文件是從本地 jar 中讀取的。
2.兩種解析器
EntityResolver 就是用來處理 XML 驗證的。我們先來看下 EntityResolver 接口的定義:
public?interface?EntityResolver?{????public?abstract?InputSource?resolveEntity?(String?publicId,???????????????????????????????????????????????String?systemId)????????throws?SAXException,?IOException;}接口中就只有一個方法,就是加載約束文件。在 Spring 中,EntityResolver 的實現(xiàn)類是 DelegatingEntityResolver:
public?class?DelegatingEntityResolver?implements?EntityResolver?{?public?static?final?String?DTD_SUFFIX?=?".dtd";?public?static?final?String?XSD_SUFFIX?=?".xsd";?private?final?EntityResolver?dtdResolver;?private?final?EntityResolver?schemaResolver;?public?DelegatingEntityResolver(@Nullable?ClassLoader?classLoader)?{??this.dtdResolver?=?new?BeansDtdResolver();??this.schemaResolver?=?new?PluggableSchemaResolver(classLoader);?}?public?DelegatingEntityResolver(EntityResolver?dtdResolver,?EntityResolver?schemaResolver)?{??this.dtdResolver?=?dtdResolver;??this.schemaResolver?=?schemaResolver;?}?@Override?@Nullable?public?InputSource?resolveEntity(@Nullable?String?publicId,?@Nullable?String?systemId)???throws?SAXException,?IOException?{??if?(systemId?!=?null)?{???if?(systemId.endsWith(DTD_SUFFIX))?{????return?this.dtdResolver.resolveEntity(publicId,?systemId);???}???else?if?(systemId.endsWith(XSD_SUFFIX))?{????return?this.schemaResolver.resolveEntity(publicId,?systemId);???}??}??return?null;?}?@Override?public?String?toString()?{??return?"EntityResolver?delegating?"?+?XSD_SUFFIX?+?"?to?"?+?this.schemaResolver?+????"?and?"?+?DTD_SUFFIX?+?"?to?"?+?this.dtdResolver;?}}在 DelegatingEntityResolver 類中:
由于現(xiàn)在大部分都是 schema 約束,所以這里我們就來重點看下 PluggableSchemaResolver 類的實現(xiàn):
public?class?PluggableSchemaResolver?implements?EntityResolver?{?public?static?final?String?DEFAULT_SCHEMA_MAPPINGS_LOCATION?=?"META-INF/spring.schemas";?private?static?final?Log?logger?=?LogFactory.getLog(PluggableSchemaResolver.class);?@Nullable?private?final?ClassLoader?classLoader;?private?final?String?schemaMappingsLocation;?@Nullable?private?volatile?Map?schemaMappings;?public?PluggableSchemaResolver(@Nullable?ClassLoader?classLoader)?{??this.classLoader?=?classLoader;??this.schemaMappingsLocation?=?DEFAULT_SCHEMA_MAPPINGS_LOCATION;?}?public?PluggableSchemaResolver(@Nullable?ClassLoader?classLoader,?String?schemaMappingsLocation)?{??Assert.hasText(schemaMappingsLocation,?"'schemaMappingsLocation'?must?not?be?empty");??this.classLoader?=?classLoader;??this.schemaMappingsLocation?=?schemaMappingsLocation;?}?@Override?@Nullable?public?InputSource?resolveEntity(@Nullable?String?publicId,?@Nullable?String?systemId)?throws?IOException?{??if?(logger.isTraceEnabled())?{???logger.trace("Trying?to?resolve?XML?entity?with?public?id?["?+?publicId?+?????"]?and?system?id?["?+?systemId?+?"]");??}??if?(systemId?!=?null)?{???String?resourceLocation?=?getSchemaMappings().get(systemId);???if?(resourceLocation?==?null?&&?systemId.startsWith("https:"))?{????resourceLocation?=?getSchemaMappings().get("http:"?+?systemId.substring(6));???}???if?(resourceLocation?!=?null)?{????Resource?resource?=?new?ClassPathResource(resourceLocation,?this.classLoader);????try?{?????InputSource?source?=?new?InputSource(resource.getInputStream());?????source.setPublicId(publicId);?????source.setSystemId(systemId);?????if?(logger.isTraceEnabled())?{??????logger.trace("Found?XML?schema?["?+?systemId?+?"]?in?classpath:?"?+?resourceLocation);?????}?????return?source;????}????catch?(FileNotFoundException?ex)?{?????if?(logger.isDebugEnabled())?{??????logger.debug("Could?not?find?XML?schema?["?+?systemId?+?"]:?"?+?resource,?ex);?????}????}???}??}??return?null;?}?private?Map?getSchemaMappings()?{??Map?schemaMappings?=?this.schemaMappings;??if?(schemaMappings?==?null)?{???synchronized?(this)?{????schemaMappings?=?this.schemaMappings;????if?(schemaMappings?==?null)?{?????try?{??????Properties?mappings?=????????PropertiesLoaderUtils.loadAllProperties(this.schemaMappingsLocation,?this.classLoader);??????schemaMappings?=?new?ConcurrentHashMap<>(mappings.size());??????CollectionUtils.mergePropertiesIntoMap(mappings,?schemaMappings);??????this.schemaMappings?=?schemaMappings;?????}?????catch?(IOException?ex)?{??????throw?new?IllegalStateException(????????"Unable?to?load?schema?mappings?from?location?["?+?this.schemaMappingsLocation?+?"]",?ex);?????}????}???}??}??return?schemaMappings;?}?@Override?public?String?toString()?{??return?"EntityResolver?using?schema?mappings?"?+?getSchemaMappings();?}}在上篇文章中,我們獲取 EntityResolver 是通過 getEntityResolver 方法來獲取的:
protected?EntityResolver?getEntityResolver()?{?if?(this.entityResolver?==?null)?{??//?Determine?default?EntityResolver?to?use.??ResourceLoader?resourceLoader?=?getResourceLoader();??if?(resourceLoader?!=?null)?{???this.entityResolver?=?new?ResourceEntityResolver(resourceLoader);??}??else?{???this.entityResolver?=?new?DelegatingEntityResolver(getBeanClassLoader());??}?}?return?this.entityResolver;}這里最終返回的是 ResourceEntityResolver,ResourceEntityResolver 繼承自 DelegatingEntityResolver,當(dāng)調(diào)用 resolveEntity 方法時,也是先調(diào)用父類的該方法,進(jìn)行處理,如果父類方法處理成功了,就直接返回父類方法給出的結(jié)果,如果父類方法處理失敗了,則在 ResourceEntityResolver 中通過資源的相對路徑再次嘗試加載。
3.小結(jié)
好啦,經(jīng)過上面的介紹,相信大家對于 XMl 約束和 EntityResolver 都有一定的了解啦。
后記
本文剛寫完,微信群里就有小伙伴問了一個一模一樣的問題:
松哥不禁感嘆,源碼并非離我們很遠(yuǎn)的東西,閱讀源碼可以有效解決我們?nèi)粘i_發(fā)中一些實實在在的問題!
如果覺得有收獲,記得點個在看鼓勵下松哥哦~搜索微信公眾號【江南一點雨】,回復(fù) 888 獲取超 17k star 開源項目學(xué)習(xí)文檔~
總結(jié)
以上是生活随笔為你收集整理的什么是spring_Spring 源码第三弹!EntityResolver 是个什么鬼?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: 1803无法升级到2004_汽车排放国标
- 下一篇: php获取周几,php怎么获得星期几
