日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

什么是spring_Spring 源码第三弹!EntityResolver 是个什么鬼?

發布時間:2024/7/5 javascript 41 豆豆
生活随笔 收集整理的這篇文章主要介紹了 什么是spring_Spring 源码第三弹!EntityResolver 是个什么鬼? 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

上篇文章和小伙伴們說了 Spring 源碼中 XML 文件的解析流程,本來可以繼續往下走看加載核心類了,但是松哥還是希望能夠慢一點,既然要學就學懂,在 XML 文件解析的過程中還涉及到一些其他的類和概念,因此我就先用幾篇文章介紹一下這些涉及到的概念或者類,然后我們再繼續往下看。

本文要和大家介紹的是上篇文章中涉及到的 EntityResolver 類,看看這個類到底是干嘛用的。

本文是 Spring 源碼系列第四篇,閱讀前面文章有助于更好理解本文:

  • Spring 源碼解讀計劃
  • Spring 源碼第一篇開整!配置文件是怎么加載的?
  • Spring 源碼第二彈!XML 文件解析流程
  • 先來回顧下,在 EntityResolver 這個類在上篇文章哪里出現了。

    我們在講到 doLoadDocument 方法時,在該方法中調用 loadDocument 方法時,傳遞的第二個參數就是一個 EntityResolver 實例,當時我們說這個是用來處理文件的驗證方式的,但是到底是怎么處理的,今天我們就來看下。

    1.XML 驗證模式

    要了解 EntityResolver,就得先來看看 XML 文件驗證模式。

    現在我們大多數情況下可能都是使用 JSON 傳遞數據,XML 使用較少,可能有的小伙伴對 XML 文件的一些規則還不太熟悉,我這里稍微說一下。

    XML 是指可擴展標記語言(eXtensible Markup Language),它是一種標記語言,類似 HTML;XML 標簽沒有被預定義,需要用戶自行定義標簽,也就是 XML 文件中的節點都是用戶自定義的。XML 文件從設計之初就是為了傳輸數據,而非顯示數據。

    一般來說,一個 XML 文件由六個部分組成:

    • 文檔生命
    • 元素
    • 屬性
    • 注釋
    • CDATA 區
    • 處理指令

    雖然說 XML 文件本身是沒有預定義 XML 標簽,但是當 XML 文件作為框架的配置時,對于 XML 標簽還是要有一定的約束,否則每個人都按照自己的喜好定義 XML 標簽,框架就沒法讀取這樣的 XML 文件了。

    在 XML 技術中,開發者可以通過一個文檔來約束一個 XML 的文檔中的標簽,這個文檔稱之為約束。遵循 XML 語法的 XML 我們稱之為格式良好的 XML,而遵循 XML 約束的 XML 我們稱之為有效的 XML。XML 約束文檔主要定義了在 XML 中允許出現的元素名稱、屬性及元素出現的順序等等。

    要想約束 XML 標簽,有兩種方式:

  • DTD
  • Schema
  • DTD(Document Type Definition),全稱為文檔類型定義,一個 DTD 約束文件我們既可以定義在 XML 文件內部,也可以定義一個本地文件,也可以引用一個網絡上的公共的 DTD。

    XML Schema 也是一種用于定義和描述 XML 文檔結構與內容的模式語言,相比于 DTD,Schema 對于名稱空間的支持更加友好,同時也支持更多的數據類型,而且它的約束能力也比較強大,另外還有非常重要的一點是,Schema 文檔本身也是 XML 文檔,而不是像 DTD 一樣使用自成一體的語法。

    所以,Schema 目前在 XML 約束這塊更具備優勢,也在逐漸替代 DTD。

    大家在日常開發中,這兩種約束可能都見過,但是有的人可能沒注意。我給大家簡單舉一個例子。

    早期的 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">????????

    現在大家看到的 Spring 配置頭部一般都是這樣,這就是 Schema 約束:

    <?xml ?version="1.0"?encoding="UTF-8"?>

    schema 約束對命名空間有著很好的支持,命名空間可以防止命名沖突,schema 中的名稱空間和約束文件都是成對出現的。

    有了約束,XML 文件中該寫什么不該寫什么就固定下來了,這樣框架才能成功解析出 XML 文件。

    但是大家同時也發現了一個新的問題,無論是 DTD 還是 Schema 約束,給出的約束文件地址都是一個在線地址,這就意味著項目啟動時必須能夠訪問到該在線地址,才能加載到約束文件,如果訪問在線約束文件失敗,那么項目啟動也會失敗。

    為了解決這個問題,框架一般都是將約束文件放在本地的,在本地哪里呢?實際上就在你下載的 jar 包里。以 spring-beans 為例,在下載的 jar 包里有如下兩個文件:

    spring.handlers 文件內容如下:

    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

    這其實一個映射配置,每一個名稱空間對應的處理類在這里進行配置。

    spring.schemas 文件內容如下(部分):

    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

    可以看到,各種版本以及沒有版本號的約束文件,都對應了同一個文件,就是 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 的實現類是 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 類中:

  • 首先通過兩種不同的后綴來區分不同的約束。
  • 然后定義了 dtdResolver 和 schemaResolver 兩個不同的變量,對應的類型分別是 BeansDtdResolver 和 PluggableSchemaResolver,也就是 dtd 和 schema 的約束驗證分別由這兩個類來處理。
  • 在 resolveEntity 方法中,根據解析出來不同的后綴,分別交由不同的 EntityResolver 來處理。resolveEntity 解析中有兩個參數,如果是 dtd 解析的話,publicId 是有值的,如果是 schema 解析,publicId 為 null,而 systemId 則始終指向具體的約束文件。
  • 由于現在大部分都是 schema 約束,所以這里我們就來重點看下 PluggableSchemaResolver 類的實現:

    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();?}}
  • 在這個類中,一上來先通過 DEFAULT_SCHEMA_MAPPINGS_LOCATION 變量定義了 spring.schemas 文件的位置。
  • getSchemaMappings 方法則是將 spring.schemas 文件中的內容讀取成一個 Map 加載進來。
  • 在 resolveEntity 方法中,根據 systemId 找到文件路徑,systemId 是 http://www.springframework.org/schema/beans/spring-beans.xsd 格式,文件路徑則是 org/springframework/beans/factory/xml/spring-beans.xsd,如果第一次沒有加載到,就把用戶的 https: 替換成 http: 再去加載。
  • 有了文件路徑,接下來調用 ClassPathResource 去獲取一個 Resource 對象,這塊可以參考本系列第二篇,這里我就不再贅述。
  • 最后構造一個 InputSource 返回即可。
  • 在上篇文章中,我們獲取 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,當調用 resolveEntity 方法時,也是先調用父類的該方法,進行處理,如果父類方法處理成功了,就直接返回父類方法給出的結果,如果父類方法處理失敗了,則在 ResourceEntityResolver 中通過資源的相對路徑再次嘗試加載。

    3.小結

    好啦,經過上面的介紹,相信大家對于 XMl 約束和 EntityResolver 都有一定的了解啦。

    后記

    本文剛寫完,微信群里就有小伙伴問了一個一模一樣的問題:

    松哥不禁感嘆,源碼并非離我們很遠的東西,閱讀源碼可以有效解決我們日常開發中一些實實在在的問題!

    如果覺得有收獲,記得點個在看鼓勵下松哥哦~搜索微信公眾號【江南一點雨】,回復 888 獲取超 17k star 開源項目學習文檔~

    總結

    以上是生活随笔為你收集整理的什么是spring_Spring 源码第三弹!EntityResolver 是个什么鬼?的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。