URL 源码分析
需要了解的知識點(diǎn):
URI、 URL 和 URN 的區(qū)別
URI 源碼分析
URL 和URI的最大區(qū)別是:
URL可以定位到一個資源,也就是說,URL類可以訪問URL指定的資源信息。
URI只是標(biāo)識一個對象,所以URI類無法獲取URI標(biāo)識的對象。
下面通過源碼來分析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òu)造方法取決你有那些信息以及信息形式。
1. 根據(jù)一個字符串形式的URL,來構(gòu)建URL對象。
2. 根據(jù) 協(xié)議、主機(jī)名、文件來構(gòu)造一個URL。
使用該協(xié)議默認(rèn)的端口,并且file參數(shù)應(yīng)當(dāng)以斜線開頭,包括文件路徑、文件名稱和片段。
3. 根據(jù) 協(xié)議、主機(jī)名、端口、文件來構(gòu)造一個URL。
4. 根據(jù) 協(xié)議、主機(jī)名、端口、文件和URLStreamHandler來構(gòu)造一個URL。
URLStreamHandler:主要是用來讀取指定的資源,并返回該資源的一個流。
5. 根據(jù)一個基礎(chǔ)URL和一個相對URL來構(gòu)建一個絕對URL。
6. 根據(jù)一個基礎(chǔ)URL和一個相對URL來構(gòu)建一個絕對URL,并傳入一個URLStreamHandler對象。
解析URL
URL主要是通過7部分組成,如下圖:
獲得URL的協(xié)議
public String getProtocol()
獲得授權(quán)機(jī)構(gòu)信息(包括用戶信息、主機(jī)和端口)
public String getAuthority()
獲得用戶信息(用戶名和密碼)
public String getUserInfo()
獲取主機(jī)地址(域名或ip地址)
public String getHost()
獲得端口
public int getPort()
獲得文件信息(路徑、文件名和查詢參數(shù))
public String getFile()
獲得路徑信息(路徑、文件名)
public String getPath()
獲取查詢參數(shù)信息
public String getQuery()
獲得片段信息
public String getRef()
獲得該協(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 獲取數(shù)據(jù)
從概念上區(qū)分:URI只是標(biāo)識一個資源,而URL可以定位一個資源。
所以java總URI只負(fù)責(zé)解析URI功能,而URL有解析URL的功能,還有獲取URL指定資源的數(shù)據(jù)。
可以通過以下5個方法來獲取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對象。URLConnection 對象可以獲取原始的文檔(如:html、純文本、二進(jìn)制圖像等),還可以獲取訪問這個協(xié)議指定的所有的元數(shù)據(jù)(如:http協(xié)議的請求頭信息)。URLConnection 對象除了從URL中讀取資源外,還允許向URL中寫入數(shù)據(jù)。(如:http post提交表單數(shù)據(jù),mailto 發(fā)送電子郵件等)
URLStreamHandler可以讓系統(tǒng)根據(jù)當(dāng)前URL協(xié)議來選擇響應(yīng)的Handler,也可以使用擴(kuò)展URLStreamHandler類來自定義實(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í)例化
如果用戶傳過來的URLStreamHandler 實(shí)例,則需要驗(yàn)證安全性問題。比如:applet程序是在客戶的瀏覽器端運(yùn)行的java程序,他如果讀取服務(wù)器上jar文件的時候就可以通過,如果讀取客戶端本地磁盤中的文件則不允許訪問。代碼如下圖:
如果用戶沒有指定URLStreamHandler實(shí)例,則通過protocol協(xié)議來決定使用哪個協(xié)議的URLStreamHandler的實(shí)例。代碼如下:
jdk的sun.net.www.protocol包中默認(rèn)支持以下幾種協(xié)議,當(dāng)然用戶也可以擴(kuò)展URLStreamHandler實(shí)例,來實(shí)現(xiàn)自定義的協(xié)議。
java中默認(rèn)支持的協(xié)議如下圖:
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 measureProxy 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());elsesm.checkConnect(epoint.getAddress().getHostAddress(), epoint.getPort());}return handler.openConnection(this, p); }可以通過URL對象設(shè)置的代理來獲取URLConnection對象。
openStream() 方法
public final InputStream openStream() throws java.io.IOException {return openConnection().getInputStream(); }直接獲取URL指定資源的流InputStream對象,該方法無法向URL中寫如數(shù)據(jù),也無法訪問這個協(xié)議的所有的元數(shù)據(jù)(如:html協(xié)議的請求頭)。
getContent() 方法
public final Object getContent() throws java.io.IOException {return openConnection().getContent(); }getContent() 方法返回由URL引用的數(shù)據(jù),嘗試由它建立某種類型的對象。如果URL指定的資源是文本類型(如:html、asciii 文件),返回的就是InputStream對象。如果URL指定的資源是圖片則返回java.awt.ImageProducer對象。
getContent(Class[] classes) 方法
final Object getContent(Class[] classes) throws java.io.IOException {return openConnection().getContent(classes); }該方法允許用戶選擇希望將內(nèi)容作為那個類型返回。
例如,如果首先將HTML文件作為一個String返回,而第二個選擇是Reader,第三個選擇是InputStream,就可以編寫一下代碼:
equals 方法
URL類的equals方法是調(diào)用URLStreamHandler對象的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方法會對比 URL指向的主機(jī)、端口、文件路徑和片段標(biāo)識。當(dāng)所有的都一樣才會返回true,但equals方法也會嘗試解析DNS,來判斷兩個主機(jī)是否相同。如:可以判斷http://localhost:8080/index.html 和 http://127.0.0.1:8080/index.html 兩個URL是相等的。
equals方法底層是調(diào)用了sameFile()方法。
注意:
因?yàn)閑quals方法有解析DNS的功能,解析DNS是一個阻塞IO操作!所以應(yīng)當(dāng)避免URL存儲在依賴equals()的數(shù)據(jù)結(jié)構(gòu)中,如HashMap。如果要存儲最后是使用URI來進(jìn)行存儲。URI的equals方法是不會解析DNS的。
sameFile(URL u1, URL u2) 方法
該方法作用和equals基本相同,這里也包括DNS解析,不過sameFile()不考慮片段標(biāo)識問題。下面通過代碼來解析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; }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 themif (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());elsereturn u1.getHost() == null && u2.getHost() == null; }該方法使用兩個URL的host來構(gòu)造InetAddress對象,調(diào)用InetAddress對象的getHost() 方法 來判斷兩個host是否一致。
InetAddress.getHost()可以通過DNS解析域名,來獲取域名綁定的ip地址。
本人簡書blog地址:http://www.jianshu.com/u/1f0067e24ff8????
點(diǎn)擊這里快速進(jìn)入簡書
GIT地址:http://git.oschina.net/brucekankan/
點(diǎn)擊這里快速進(jìn)入GIT
總結(jié)