URI,URL概念和spring中的Resouce含義的探究

目錄

1.前言

2.URI概念

1.1 Overview of URI

1.2 URI, URL, and URN

1.3 URI的舉例

1.4 URI Syntactic Components

1.4.1 Scheme Component

1.4.2 Authority Component

1.4.3 Path Component,Query Component

2. URI,URL和URLStreamHandler簡單說明

3. spring中爲什麼要對資源封裝

4. 小結


1.前言

之前因爲看到spring源碼深度解析中提到了對資源封裝的來由,覺得比較困惑,而且我也是對URI和URL的概念比較混亂,纔有了這篇的整理,源頭起源於jdk說到了一篇rfc,事實證明即便英文再不好,也得硬着頭皮看,會有意想不到的收穫。此外還有一篇博客感覺不錯,這裏直接貼鏈接:https://www.cnblogs.com/throwable/p/9740425.html

2.URI概念

URI,統一資源標識符,Uniform Resource Identifier。爲了搞懂概念,參照機器翻譯,覺得翻譯有問題的可以去看原版:https://www.ietf.org/rfc/rfc2396.txt。感覺實在無法理解的不翻譯,感覺看不懂而且感覺沒必要看的跳過。翻譯後面會加上個人的想法。

1.1 Overview of URI

URI爲標識資源提供了一種簡單且具有擴展性的方法(extensible means)。URL的語法和語義(semantics)規範(specification)源自World Wide Web global information initiative中引入的概念。。。

下面對URI這三個分別繼續解釋:

Uniform:統一性提供了幾種好處:可以在同個上下文中使用不同類型的資源標識符,即便這些訪問資源的機制並不相同;它允許不同類型的資源標識符可以有通用的語法和統一的語義解釋;它允許引入新的資源標識符類型而不會妨礙(interfering with)到現有標識符到使用;而且,它可以在不同的上下文中對標識符進行復用,從而使得新的應用類型或協議可以重新利用現有的資源標識符( to leverage a pre-existing, large, and widely-used set of resource identifiers)。可以看到後面的的解釋是說可以複用,說明在不同場景對標識符的釋義都不同,這個取決於具體的協議和使用的場景。而且我們也可以自定義一個解析的規範,具有擴展性。那如果這樣解釋和統一有什麼關係?統一的好處不就是方便使用麼,難道是定義一個較爲統一的規範和概念?

Resource:只要可以被標識,那就是一個資源。例如電子文檔,圖片,服務(如洛杉磯當天到天氣預報)以及其他資源的集合。並不是所有資源都是指網絡上可以被找到的(retrievable,檢索)。例如人,公司,甚至是圖書館中的一本書也可以被視爲資源。看來資源的定義範圍很廣泛。資源是到一個或一組實體的概念性的映射,該實體並不一定對應於任一時間點上的實體。比如說“人”這個概念是對我們這類高級動物在個體上的一種概念上的映射,但是人這個概念並不會隨着我們年齡的變化而改變,感覺。。可以這麼理解。因此,即使資源的內容(即資源當前對應的實體)隨時間而變化,只要概念的映射在這個變化的過程中,沒有發生變化,那麼資源也可以保持不變。即便是資源本身隨着時間消亡了。

Identifier:標識符是一個對象,它可以作爲對可以被標識的對象的一種引用。在URI中,這個對象就是一個受到語法限制的(restricted)字符序列。也就是說,我們人爲定義來一個規則來引用可以被標識的資源。

標識了資源後,系統就可以對該資源進行各種操作,這些操作的特徵如帶有:訪問,更新,替換,查找屬性等字樣。

1.2 URI, URL, and URN

一個URI可以被進一步分類爲a locator, a name, or both術語URL(Uniform Resource Locator,統一資源定位器)就是URI的子集,它的表示方式主要傾向於用訪問機制來標識資源,如網絡的”位置“(不過這個翻譯和後面的這個他們的網絡”位置“的舉例感覺不是很搭,乾脆直接理解爲以位置來標識資源),而不是用資源的名稱或者是其他屬性來標識。術語URN(Uniform Resource Name,統一資源名稱)也是他的一個子集,它要求即便是這個資源不復存在了或者不可用了,也不能複用,要保證一個全局的唯一性和持久性。

Although many URL schemes are named after protocols, this does not imply that the only way to access the URL's resource is via the named protocol. Gateways, proxies, caches, and name resolution services might be used to access some resources, independent of the protocol of their origin, and the resolution of some URL may require the use of more than one protocol (e.g., both DNS and HTTP are typically used to access an "http" URL's resource when it can't be found in a local cache).通過機器翻譯,這段話要說明的感覺是訪問一個資源有時候可能並不僅僅是隻靠有一個協議。

1.3 URI的舉例

下面列舉了常用的URI:

  1. ftp,文件傳輸協議:ftp://ftp.is.co.za/rfc/rfc1808.txt;
  2. gopher和gopher+協議: gopher://spinaltap.micro.umn.edu/00/Weather/California/Los%20Angeles;在www出現之後就基本沒人用了。
  3. http,超文本傳輸協議:http://www.math.uio.no/faq/compression-faq/part1.html
  4. mailto,用於電子郵件,mailto:[email protected]
  5. 新聞?news scheme for USENET news groups and articles:news:comp.infosystems.www.servers.unix
  6. 電話協議: telnet://melvyl.ucop.edu/

1.4 URI Syntactic Components

URI的語法取決於scheme。通常absolute URI如下所示:

<scheme>:<scheme-specific-part>

冒號後面具體什麼意思,取決於scheme。而且URI語法並不要求scheme-specific-part有通用的結構和語義。雖然如此,但是我們可以對URI的子集定義通用的語法,用於表示命名空間的層次關係:

<scheme>://<authority><path>?<query>

除了scheme之外,其餘的可能不一定會用到。比如說一些URI scheme並不允許出現<authority>組件,或者不允許出現<query>。

本質上(in nature)分層的URI使用斜杆“/”字符來分割分層的組件,可能會覺得和文件系統中的文件路徑很像,但是這並不就說明了資源就是文件,URI就是文件系統路徑名。e,反正就是說,雖然URI用斜杆來對層次進行分割,和文件路徑的分隔符一樣,但是二者並不相同。

absoluteURI = scheme ":" ( hier_part | opaque_part ) 
hier_part = ( net_path | abs_path ) [ "?" query ] 
net_path = "//" authority [ abs_path ] 
abs_path = "/" path_segments 
opaque_part = uric_no_slash *uric 
uric_no_slash = unreserved | escaped | ";" | "?" | ":" | "@" | "&" | "=" | "+" | "$" | ","

下面對組件一一解釋。

1.4.1 Scheme Component

正如(just as)有許多不同訪問資源的方法一樣,我們可以用不同的scheme來標識這類資源。URI的語法由一系列(a sequence of)通過保留字符(reserved characters)所分割的組件構成,而第一個組件定義了URI剩餘部分的語義。shcema的名稱由一系列字符組成,開頭是小寫的字母,後面可以是小寫字母,數字,加號,點號(period),-連字符(hyphen)。

scheme = alpha *( alpha | digit | "+" | "-" | "." )

雖然我貼了很多這種,但是目前還沒看懂意思 

1.4.2 Authority Component

看起來說的是授權用的(a naming authority),有兩種:Registry-based Naming Authority和Server-based Naming Authority。前面那種沒看懂看名稱說的是你只有註冊了才能用,也就是說可能有一個權威的機構管理這個;後面的是基於ip協議的:

<userinfo>@<host>:<port>

userinfo就是訪問服務器所需的授權信息,可能是用戶名密碼等等,例如:user:password。但是因爲是明文(clear text),所以不推薦。

1.4.3 Path Component,Query Component

路徑組件標識了特定scheme或authority組件範圍中的資源,這個資源路徑不一定就是文件路徑這個要看定義。查詢組件的含義有資源本身來釋義,比如說數據庫查詢服務,可能是就是傳輸查詢條件。

需要注意的是到目前爲止,說到的都是一個層次結構,是我們自己定義的一個通用的結構,屬於URI的一個子集。

2. URI,URL和URLStreamHandler簡單說明

看了上面的介紹,雖然後面有點混亂,但是基本上對URI有了個概念,現在看看java中的URI類。首先是URI的屬性:

// -- Properties and components of this instance --
// Components of all URIs: [<scheme>:]<scheme-specific-part>[#<fragment>]
private transient String scheme;            // null ==> relative URI
private transient String fragment;

// Hierarchical URI components: [//<authority>]<path>[?<query>]
private transient String authority;         // Registry or server

// Server-based authority: [<userInfo>@]<host>[:<port>]
private transient String userInfo;
private transient String host;              // null ==> registry-based
private transient int port = -1;            // -1 ==> undefined

// Remaining components of hierarchical URIs
private transient String path;              // null ==> opaque
private transient String query;

// The remaining fields may be computed on demand
//...

可以看到這些屬性和之前說的各個組件是對應的,說明URI是按照層次規範來實現的。

從構造函數進入,傳入一個字符串會進行解析操作,這個解析器是一個內部類,目的在於校驗其格式的合法,並將各個屬性提取便於我們獲取,至於如何校驗就算了,主要是看的頭疼,再者就是目前也沒有必要非要去看懂:

// [<scheme>:]<scheme-specific-part>[#<fragment>]
//
void parse(boolean rsa) throws URISyntaxException {
   //...
}

也就是說URI本身並沒有任何訪問資源的能力

而URL則不同,可以看到URL中除了一些組件屬性之外,還有handler的處理,也就是說訪問資源交給了URLStreamHandler的實現去處理:

//比較兩個是否是標識同個資源,不過比較的時候不包括fragment
public boolean sameFile(URL other) {
    return handler.sameFile(this, other);
}
//表示URL引用到資源的連接
//這裏創建的連接並不是實際的,而是邏輯的,真正的連接實在connect的時候
//URLConnection也是有很多子類的,根據協議的不同
public URLConnection openConnection() throws java.io.IOException {
    return handler.openConnection(this);
}

具體的獲取:

static URLStreamHandler getURLStreamHandler(String protocol) {
    //初始化裏面只有一個file協議的處理器
    URLStreamHandler handler = handlers.get(protocol);
    if (handler == null) {
        boolean checkedWithFactory = false;

        // Use the factory (if any) 使用工廠參加,默認情況下是空的,我們可以自己實現
        if (factory != null) {
            handler = factory.createURLStreamHandler(protocol);
            checkedWithFactory = true;
        }
        // Try java protocol handler
        if (handler == null) {
            //後面就是計算包名:sun.net.www.protocol.協議.Handler,反射實例化
            //...
        }
    }
    //...
}

可見具體的Handler都在這個包下面,協議的實現就是這些:

而對於工廠來說,也有一個默認的實現:

//工廠中只有一個接口,那就是創建url流處理器
public interface URLStreamHandlerFactory {
    URLStreamHandler createURLStreamHandler(String protocol);
}
//Launcher,這個默認的實現也是從指定包名中反射實例化
private static class Factory implements URLStreamHandlerFactory {
    private static String PREFIX = "sun.net.www.protocol";

    private Factory() {
    }

    public URLStreamHandler createURLStreamHandler(String var1) {
        String var2 = PREFIX + "." + var1 + ".Handler";

        try {
            Class var3 = Class.forName(var2);
            return (URLStreamHandler)var3.newInstance();
        } catch (ReflectiveOperationException var4) {
            throw new InternalError("could not load " + var1 + "system protocol handler", var4);
        }
    }
}

現在腦子裏面現在可以形成一個uml圖,因爲不想畫了:

  1. URL持有URLStreamHandler的接口,URL並不需要知道接口實現是什麼,它只要利用處理器提供的創建接口,來構造一個連接URLConnection,獲取輸入輸出流,或者是子類連接提供的特定接口進行操作,最後關閉。
  2. URLStreamHandler下面有很多子實現類Handler,根據協議類分包存放
  3. 同樣URLConnection下面也有很多實現的子類

這裏並不是要解析實現源碼,而是爲了銜接spring源碼深度解析中,關於資源封裝的概念。所以直接進入最後一步

3. spring中爲什麼要對資源封裝

spring源碼深度解析中提到:

在Java中,將不同來源的資源抽象成URL,通過註冊不同的handler(URLStreamHandler)來處理不同來源的資源讀取邏輯,一般來說,通過不同的前綴來進行識別,如file:,http:,jar:。

但是URL並沒有默認定義對classpath或ServletContext等資源的的handler,雖然我們可以註冊自己的URLStreamHandler來解析特定的前綴,如classpath:,但是這就需要了解URL的實現機制,而且URL並沒有提供可以檢查當前資源是否可讀,是否存在的方法。

基於上面的原因,spring對內部使用的資源實現了自己的抽象結構,也就是Resource。

這段話我之前看到的時候就很困惑,直到我整理出上面的那些。這個時候我們就可以很清楚的知道,標識符並不能說明資源是否存在,是否可讀等,而且也並沒有提供關於classpath的協議解析。也就是說一切爲了便捷,和符合需求,而且爲了可以複用代碼他也提供了和URL和URI的轉換。

public interface InputStreamSource {
   InputStream getInputStream() throws IOException;
}

InputStreamSource封裝任何可以返回InputStream的類,比如File,ClassPath下的資源和ByteArray資源等。

Resource接口抽象了所有Spring內部使用到的底層資源:File,URL,Classpath等。

public interface Resource extends InputStreamSource {
    boolean exists(); //存在性
    boolean isReadable(); //可讀性
    boolean isOpen(); //是否處於打開狀態
    //提供了不同資源到URL,URI,File之間的轉換,以便獲取下面的信息
    URL getURL() throws IOException;
    URI getURI() throws IOException;
    File getFile() throws IOException;
    
    long contentLength() throws IOException;
    long lastModified() throws IOException;
    //爲了便於操作,提供了基於當前資源,創建相對資源的方法
    Resource createRelative(String relativePath) throws IOException;
    String getFilename();
    //可以在錯誤信息中使用,打印描述符信息
    String getDescription();
}

對於不同來源的資源文件,都有對應的Resouce實現,如File資源(FileSystemResource)等。

4. 小結

只要可以被標識,就可以當成資源,這個不侷限於計算機的世界,現實世界也是。同理URI及其子集也不僅僅侷限於計算機世界。資源是一種概念的抽象,它映射的實體可以是一個也可以是一組。而根據使用的方式不同或者說資源的屬性,可以分爲URL和URN或者其他子集。資源一旦被標識,我就可以據此進行訪問和操作,比如說我們的住址就可以進行快遞傳輸,身份證的標識,就可以刷票等。資源等訪問方式有很多種,條條大路通羅馬,因爲訪問的機制,我們才需要定義各種scheme。

回到java上,我們爲URL的子集定義了一個通用的具有層次性的規範,可以看到java中的URI和rfc中說明的很多是一樣的。對於URL,它本身裏面實現了一些默認的協議,對資源的訪問,將其抽象爲了連接URLConnection,通過這個對連接進行操作。但是可以看到這些協議並沒有對classpath進行解析,而且URL本身並不能說明資源是否存在是否可以交互,所以自己封裝Resouce,但是其實在真正用的時候,很多地方也是用jdk封裝的,畢竟重複造輪子沒必要,一切爲了需求。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章