需要了解的知識(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)造方法取決你有那些信息以及信息形式百姓。
- 根據(jù)一個(gè)字符串形式的URL,來(lái)構(gòu)建URL對(duì)象况木。
- 根據(jù) 協(xié)議垒拢、主機(jī)名旬迹、文件來(lái)構(gòu)造一個(gè)URL。
使用該協(xié)議默認(rèn)的端口求类,并且file參數(shù)應(yīng)當(dāng)以斜線開(kāi)頭奔垦,包括文件路徑、文件名稱和片段仑嗅。 - 根據(jù) 協(xié)議宴倍、主機(jī)名、端口仓技、文件來(lái)構(gòu)造一個(gè)URL鸵贬。
- 根據(jù) 協(xié)議、主機(jī)名脖捻、端口阔逼、文件和URLStreamHandler來(lái)構(gòu)造一個(gè)URL。
URLStreamHandler:主要是用來(lái)讀取指定的資源地沮,并返回該資源的一個(gè)流嗜浮。 - 根據(jù)一個(gè)基礎(chǔ)URL和一個(gè)相對(duì)URL來(lái)構(gòu)建一個(gè)絕對(duì)URL。
- 根據(jù)一個(gè)基礎(chǔ)URL和一個(gè)相對(duì)URL來(lái)構(gòu)建一個(gè)絕對(duì)URL摩疑,并傳入一個(gè)URLStreamHandler對(duì)象危融。
解析URL
URL主要是通過(guò)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)識(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í)例。代碼如下:
jdk的sun.net.www.protocol包中默認(rèn)支持以下幾種協(xié)議翅敌,當(dāng)然用戶也可以擴(kuò)展URLStreamHandler實(shí)例羞福,來(lái)實(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 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.html 和 http://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;
}
- 判斷兩個(gè)URL的協(xié)議是否一致
- 判斷兩個(gè)URL的獲得文件信息是否一致状蜗。文件信息包括:路徑需五、文件名和查詢參數(shù)
- 判斷兩個(gè)URL的端口是否一致
- 判斷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)