URL 源碼分析

需要了解的知識(shí)點(diǎn):
URI凡恍、 URL 和 URN 的區(qū)別
URI 源碼分析

URL 和URI的最大區(qū)別是:
URL可以定位到一個(gè)資源锐极,也就是說(shuō),URL類可以訪問(wèn)URL指定的資源信息收捣。
URI只是標(biāo)識(shí)一個(gè)對(duì)象缀辩,所以URI類無(wú)法獲取URI標(biāo)識(shí)的對(duì)象。

下面通過(guò)源碼來(lái)分析URL類的實(shí)現(xiàn)細(xì)節(jié):

構(gòu)造

public URL(String spec);
public URL(String protocol, String host, String file);
public URL(String protocol, String host, int port, String file)
public URL(String protocol, String host, int port, String file,
               URLStreamHandler handler);
public URL(URL context, String spec);
public URL(URL context, String spec, URLStreamHandler handler);

URL提供了6種不同的構(gòu)造方法弱睦,使用那個(gè)構(gòu)造方法取決你有那些信息以及信息形式百姓。

  1. 根據(jù)一個(gè)字符串形式的URL,來(lái)構(gòu)建URL對(duì)象况木。
  2. 根據(jù) 協(xié)議垒拢、主機(jī)名旬迹、文件來(lái)構(gòu)造一個(gè)URL。
    使用該協(xié)議默認(rèn)的端口求类,并且file參數(shù)應(yīng)當(dāng)以斜線開(kāi)頭奔垦,包括文件路徑、文件名稱和片段仑嗅。
  3. 根據(jù) 協(xié)議宴倍、主機(jī)名、端口仓技、文件來(lái)構(gòu)造一個(gè)URL鸵贬。
  4. 根據(jù) 協(xié)議、主機(jī)名脖捻、端口阔逼、文件和URLStreamHandler來(lái)構(gòu)造一個(gè)URL。
    URLStreamHandler:主要是用來(lái)讀取指定的資源地沮,并返回該資源的一個(gè)流嗜浮。
  5. 根據(jù)一個(gè)基礎(chǔ)URL和一個(gè)相對(duì)URL來(lái)構(gòu)建一個(gè)絕對(duì)URL。
  6. 根據(jù)一個(gè)基礎(chǔ)URL和一個(gè)相對(duì)URL來(lái)構(gòu)建一個(gè)絕對(duì)URL摩疑,并傳入一個(gè)URLStreamHandler對(duì)象危融。

解析URL

URL主要是通過(guò)7部分組成,如下圖:

URL格式
  1. 獲得URL的協(xié)議
    public String getProtocol()

  2. 獲得授權(quán)機(jī)構(gòu)信息(包括用戶信息雷袋、主機(jī)和端口)
    public String getAuthority()

  3. 獲得用戶信息(用戶名和密碼)
    public String getUserInfo()

  4. 獲取主機(jī)地址(域名或ip地址)
    public String getHost()

  5. 獲得端口
    public int getPort()

  6. 獲得文件信息(路徑吉殃、文件名和查詢參數(shù))
    public String getFile()

  7. 獲得路徑信息(路徑、文件名)
    public String getPath()

  8. 獲取查詢參數(shù)信息
    public String getQuery()

  9. 獲得片段信息
    public String getRef()

  10. 獲得該協(xié)議默認(rèn)端口
    public int getDefaultPort()

解析URL 示例

URL url = new URL("http://user:pass@localhost:8080/infcn/index.html?type=type1#aaa");
System.out.println("protocol   :\t"+url.getProtocol());
System.out.println("authority  :\t"+url.getAuthority());
System.out.println("userinfo   :\t"+url.getUserInfo());
System.out.println("host       :\t"+url.getHost());
System.out.println("port       :\t"+url.getPort());
System.out.println("file       :\t"+url.getFile());
System.out.println("path       :\t"+url.getPath());
System.out.println("query      :\t"+url.getQuery());
System.out.println("ref        :\t"+url.getRef());
System.out.println("defaultport:\t"+url.getDefaultPort());
URL 解析

URL 獲取數(shù)據(jù)

從概念上區(qū)分:URI只是標(biāo)識(shí)一個(gè)資源楷怒,而URL可以定位一個(gè)資源蛋勺。
所以java總URI只負(fù)責(zé)解析URI功能,而URL有解析URL的功能鸠删,還有獲取URL指定資源的數(shù)據(jù)抱完。

可以通過(guò)以下5個(gè)方法來(lái)獲取URL指定的資源數(shù)據(jù)
public URLConnection openConnection();
public URLConnection openConnection(Proxy proxy);
public final InputStream openStream();
public final Object getContent();
public final Object getContent(Class[] classes);

openConnection()方法

public URLConnection openConnection() throws java.io.IOException {
    return handler.openConnection(this);
}

直接調(diào)用URLStreamHandler.openConnection()方法獲取URLConnection對(duì)象。URLConnection 對(duì)象可以獲取原始的文檔(如:html刃泡、純文本巧娱、二進(jìn)制圖像等),還可以獲取訪問(wèn)這個(gè)協(xié)議指定的所有的元數(shù)據(jù)(如:http協(xié)議的請(qǐng)求頭信息)捅僵。URLConnection 對(duì)象除了從URL中讀取資源外家卖,還允許向URL中寫入數(shù)據(jù)。(如:http post提交表單數(shù)據(jù)庙楚,mailto 發(fā)送電子郵件等)

URLStreamHandler可以讓系統(tǒng)根據(jù)當(dāng)前URL協(xié)議來(lái)選擇響應(yīng)的Handler,也可以使用擴(kuò)展URLStreamHandler類來(lái)自定義實(shí)現(xiàn)相應(yīng)資源的獲取趴樱,也可以擴(kuò)展協(xié)議馒闷。

URLStreamHandler 類結(jié)構(gòu)

public abstract class URLStreamHandler{
    abstract protected URLConnection openConnection(URL u) throws IOException;

    protected URLConnection openConnection(URL u, Proxy p) throws IOException {
        throw new UnsupportedOperationException("Method not implemented.");
    }

    protected void parseURL(URL u, String spec, int start, int limit){
        ...
    }
    protected int getDefaultPort() {
        return -1;
    }

    protected boolean equals(URL u1, URL u2) {
        String ref1 = u1.getRef();
        String ref2 = u2.getRef();
        return (ref1 == ref2 || (ref1 != null && ref1.equals(ref2))) &&
               sameFile(u1, u2);
    }

    protected int hashCode(URL u){
        ...
    }

    protected boolean sameFile(URL u1, URL u2) {
        // Compare the protocols.
        if (!((u1.getProtocol() == u2.getProtocol()) ||
              (u1.getProtocol() != null &&
               u1.getProtocol().equalsIgnoreCase(u2.getProtocol()))))
            return false;

        // Compare the files.
        if (!(u1.getFile() == u2.getFile() ||
              (u1.getFile() != null && u1.getFile().equals(u2.getFile()))))
            return false;

        // Compare the ports.
        int port1, port2;
        port1 = (u1.getPort() != -1) ? u1.getPort() : u1.handler.getDefaultPort();
        port2 = (u2.getPort() != -1) ? u2.getPort() : u2.handler.getDefaultPort();
        if (port1 != port2)
            return false;

        // Compare the hosts.
        if (!hostsEqual(u1, u2))
            return false;

        return true;
    }

    ......
}

由此類可以看出酪捡,子類必須覆蓋的方法是openConnection(URL u)方法,如果該協(xié)議支持代理模式纳账,則也需要覆蓋openConnection(URL u, Proxy p)方法逛薇。

URLStreamHandler 實(shí)例化

如果用戶傳過(guò)來(lái)的URLStreamHandler 實(shí)例,則需要驗(yàn)證安全性問(wèn)題疏虫。比如:applet程序是在客戶的瀏覽器端運(yùn)行的java程序永罚,他如果讀取服務(wù)器上jar文件的時(shí)候就可以通過(guò),如果讀取客戶端本地磁盤中的文件則不允許訪問(wèn)卧秘。代碼如下圖:


安全檢查

如果用戶沒(méi)有指定URLStreamHandler實(shí)例呢袱,則通過(guò)protocol協(xié)議來(lái)決定使用哪個(gè)協(xié)議的URLStreamHandler的實(shí)例。代碼如下:


Paste_Image.png

jdk的sun.net.www.protocol包中默認(rèn)支持以下幾種協(xié)議翅敌,當(dāng)然用戶也可以擴(kuò)展URLStreamHandler實(shí)例羞福,來(lái)實(shí)現(xiàn)自定義的協(xié)議。
java中默認(rèn)支持的協(xié)議如下圖:

Paste_Image.png

openConnection(Proxy proxy) 方法

public URLConnection openConnection(Proxy proxy) throws java.io.IOException {
    if (proxy == null) {
        throw new IllegalArgumentException("proxy can not be null");
    }

    // Create a copy of Proxy as a security measure
    Proxy p = proxy == Proxy.NO_PROXY ? Proxy.NO_PROXY : sun.net.ApplicationProxy.create(proxy);
    SecurityManager sm = System.getSecurityManager();
    if (p.type() != Proxy.Type.DIRECT && sm != null) {
        InetSocketAddress epoint = (InetSocketAddress) p.address();
        if (epoint.isUnresolved())
            sm.checkConnect(epoint.getHostName(), epoint.getPort());
        else
            sm.checkConnect(epoint.getAddress().getHostAddress(), epoint.getPort());
    }
    return handler.openConnection(this, p);
}

可以通過(guò)URL對(duì)象設(shè)置的代理來(lái)獲取URLConnection對(duì)象洒嗤。

openStream() 方法

public final InputStream openStream() throws java.io.IOException {
    return openConnection().getInputStream();
}

直接獲取URL指定資源的流InputStream對(duì)象葵硕,該方法無(wú)法向URL中寫如數(shù)據(jù)沼撕,也無(wú)法訪問(wèn)這個(gè)協(xié)議的所有的元數(shù)據(jù)(如:html協(xié)議的請(qǐng)求頭)。

getContent() 方法

public final Object getContent() throws java.io.IOException {
    return openConnection().getContent();
}

getContent() 方法返回由URL引用的數(shù)據(jù)张峰,嘗試由它建立某種類型的對(duì)象。如果URL指定的資源是文本類型(如:html棒旗、asciii 文件)喘批,返回的就是InputStream對(duì)象。如果URL指定的資源是圖片則返回java.awt.ImageProducer對(duì)象嗦哆。

getContent(Class[] classes) 方法

final Object getContent(Class[] classes) throws java.io.IOException {
    return openConnection().getContent(classes);
}

該方法允許用戶選擇希望將內(nèi)容作為那個(gè)類型返回谤祖。
例如,如果首先將HTML文件作為一個(gè)String返回老速,而第二個(gè)選擇是Reader粥喜,第三個(gè)選擇是InputStream,就可以編寫一下代碼:

URL u = new URL("http://www.jijianshuai.com");
Class<?> types = new Class[3];
types[0] = String.class;
types[1] = Reader.class;
types[2] = InputStream.class;
Object obj = u.getContent(types);

equals 方法

URL類的equals方法是調(diào)用URLStreamHandler對(duì)象的equals方法橘券。

protected boolean equals(URL u1, URL u2) {
    String ref1 = u1.getRef();
    String ref2 = u2.getRef();
    return (ref1 == ref2 || (ref1 != null && ref1.equals(ref2))) &&
           sameFile(u1, u2);
    }

equals方法會(huì)對(duì)比 URL指向的主機(jī)额湘、端口、文件路徑和片段標(biāo)識(shí)旁舰。當(dāng)所有的都一樣才會(huì)返回true锋华,但equals方法也會(huì)嘗試解析DNS,來(lái)判斷兩個(gè)主機(jī)是否相同箭窜。如:可以判斷http://localhost:8080/index.htmlhttp://127.0.0.1:8080/index.html 兩個(gè)URL是相等的毯焕。
equals方法底層是調(diào)用了sameFile()方法。

注意:
因?yàn)閑quals方法有解析DNS的功能,解析DNS是一個(gè)阻塞IO操作纳猫!所以應(yīng)當(dāng)避免URL存儲(chǔ)在依賴equals()的數(shù)據(jù)結(jié)構(gòu)中婆咸,如HashMap。如果要存儲(chǔ)最后是使用URI來(lái)進(jìn)行存儲(chǔ)芜辕。URI的equals方法是不會(huì)解析DNS的尚骄。

sameFile(URL u1, URL u2) 方法

該方法作用和equals基本相同,這里也包括DNS解析侵续,不過(guò)sameFile()不考慮片段標(biāo)識(shí)問(wèn)題倔丈。下面通過(guò)代碼來(lái)解析sameFIle的實(shí)現(xiàn)。

protected boolean sameFile(URL u1, URL u2) {
    if (!((u1.getProtocol() == u2.getProtocol()) ||
          (u1.getProtocol() != null &&
           u1.getProtocol().equalsIgnoreCase(u2.getProtocol()))))
        return false;

    if (!(u1.getFile() == u2.getFile() ||
          (u1.getFile() != null && u1.getFile().equals(u2.getFile()))))
        return false;

    int port1, port2;
    port1 = (u1.getPort() != -1) ? u1.getPort() : u1.handler.getDefaultPort();
    port2 = (u2.getPort() != -1) ? u2.getPort() : u2.handler.getDefaultPort();
    if (port1 != port2)
        return false;

    if (!hostsEqual(u1, u2))
        return false;

    return true;
}
  1. 判斷兩個(gè)URL的協(xié)議是否一致
  2. 判斷兩個(gè)URL的獲得文件信息是否一致状蜗。文件信息包括:路徑需五、文件名和查詢參數(shù)
  3. 判斷兩個(gè)URL的端口是否一致
  4. 判斷host是否一致。調(diào)用hostsEqual方法來(lái)判斷诗舰。

hostsEqual(URL u1, URL u2) 方法

protected boolean hostsEqual(URL u1, URL u2) {
    InetAddress a1 = getHostAddress(u1);
    InetAddress a2 = getHostAddress(u2);
    // if we have internet address for both, compare them
    if (a1 != null && a2 != null) {
        return a1.equals(a2);
    // else, if both have host names, compare them
    } else if (u1.getHost() != null && u2.getHost() != null)
        return u1.getHost().equalsIgnoreCase(u2.getHost());
     else
        return u1.getHost() == null && u2.getHost() == null;
}

該方法使用兩個(gè)URL的host來(lái)構(gòu)造InetAddress對(duì)象警儒,調(diào)用InetAddress對(duì)象的getHost() 方法 來(lái)判斷兩個(gè)host是否一致。
InetAddress.getHost()可以通過(guò)DNS解析域名眶根,來(lái)獲取域名綁定的ip地址蜀铲。


想了解更多精彩內(nèi)容請(qǐng)關(guān)注我的公眾號(hào)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市属百,隨后出現(xiàn)的幾起案子记劝,更是在濱河造成了極大的恐慌,老刑警劉巖族扰,帶你破解...
    沈念sama閱讀 221,576評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件厌丑,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡渔呵,警方通過(guò)查閱死者的電腦和手機(jī)怒竿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,515評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)扩氢,“玉大人耕驰,你說(shuō)我怎么就攤上這事÷疾颍” “怎么了朦肘?”我有些...
    開(kāi)封第一講書人閱讀 168,017評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)双饥。 經(jīng)常有香客問(wèn)我媒抠,道長(zhǎng),這世上最難降的妖魔是什么咏花? 我笑而不...
    開(kāi)封第一講書人閱讀 59,626評(píng)論 1 296
  • 正文 為了忘掉前任趴生,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘冲秽。我一直安慰自己舍咖,他們只是感情好矩父,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,625評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布锉桑。 她就那樣靜靜地躺著,像睡著了一般窍株。 火紅的嫁衣襯著肌膚如雪民轴。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 52,255評(píng)論 1 308
  • 那天球订,我揣著相機(jī)與錄音后裸,去河邊找鬼。 笑死冒滩,一個(gè)胖子當(dāng)著我的面吹牛微驶,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播开睡,決...
    沈念sama閱讀 40,825評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼因苹,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了篇恒?” 一聲冷哼從身側(cè)響起扶檐,我...
    開(kāi)封第一講書人閱讀 39,729評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎胁艰,沒(méi)想到半個(gè)月后款筑,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,271評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡腾么,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,363評(píng)論 3 340
  • 正文 我和宋清朗相戀三年奈梳,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片解虱。...
    茶點(diǎn)故事閱讀 40,498評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡攘须,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出饭寺,到底是詐尸還是另有隱情阻课,我是刑警寧澤,帶...
    沈念sama閱讀 36,183評(píng)論 5 350
  • 正文 年R本政府宣布艰匙,位于F島的核電站限煞,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏员凝。R本人自食惡果不足惜署驻,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,867評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧旺上,春花似錦瓶蚂、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 32,338評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至征候,卻和暖如春杭攻,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背疤坝。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,458評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工兆解, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人跑揉。 一個(gè)月前我還...
    沈念sama閱讀 48,906評(píng)論 3 376
  • 正文 我出身青樓锅睛,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親历谍。 傳聞我的和親對(duì)象是個(gè)殘疾皇子现拒,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,507評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)扮饶,斷路器具练,智...
    卡卡羅2017閱讀 134,697評(píng)論 18 139
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語(yǔ)法,類相關(guān)的語(yǔ)法甜无,內(nèi)部類的語(yǔ)法扛点,繼承相關(guān)的語(yǔ)法,異常的語(yǔ)法岂丘,線程的語(yǔ)...
    子非魚_t_閱讀 31,662評(píng)論 18 399
  • 前言 多年以前自學(xué)Java陵究,在本地做了一些筆記。最近幾年流行播客奥帘,一方面防止丟失铜邮,一方面可以幫助其他小伙伴...
    chaohx閱讀 1,033評(píng)論 0 3
  • 1 XML解析No29 【 XML:可拓展標(biāo)記語(yǔ)言,語(yǔ)言和HTML類似寨蹋,也是一種標(biāo)記語(yǔ)言松蒜。 特點(diǎn):標(biāo)記是自定義...
    征程_Journey閱讀 1,656評(píng)論 0 9
  • 一直想著要減肥,但是卻總是為自己找借口已旧; 第一次秸苗,在室友的帶領(lǐng)下,差不多一小時(shí)跑了7公里运褪,原來(lái)惊楼,我是可以跑這么長(zhǎng)時(shí)...
    快樂(lè)的小胖胖閱讀 470評(píng)論 1 1