需要了解的知识点:
URL 和URI的最大区别是:
URL可以定位到一个资源,也就是说,URL类可以访问URL指定的资源信息。
URI只是标识一个对象,所以URI类无法获取URI标识的对象。
下面通过源码来分析URL类的实现细节:
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种不同的构造方法,使用那个构造方法取决你有那些信息以及信息形式。
URL主要是通过7部分组成,如下图:
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 解析
从概念上区分:URI只是标识一个资源,而URL可以定位一个资源。
所以java总URI只负责解析URI功能,而URL有解析URL的功能,还有获取URL指定资源的数据。
可以通过以下5个方法来获取URL指定的资源数据
public URLConnection openConnection();
public URLConnection openConnection(Proxy proxy);
public final InputStream openStream();
public final Object getContent();
public final Object getContent(Class[] classes);
public URLConnection openConnection() throws java.io.IOException {
return handler.openConnection(this);
}
直接调用URLStreamHandler.openConnection()方法获取URLConnection对象。URLConnection 对象可以获取原始的文档(如:html、纯文本、二进制图像等),还可以获取访问这个协议指定的所有的元数据(如:http协议的请求头信息)。URLConnection 对象除了从URL中读取资源外,还允许向URL中写入数据。(如:http post提交表单数据,mailto 发送电子邮件等)
URLStreamHandler可以让系统根据当前URL协议来选择响应的Handler,也可以使用扩展URLStreamHandler类来自定义实现相应资源的获取,也可以扩展协议。
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)方法,如果该协议支持代理模式,则也需要覆盖openConnection(URL u, Proxy p)方法。
如果用户传过来的URLStreamHandler 实例,则需要验证安全性问题。比如:applet程序是在客户的浏览器端运行的java程序,他如果读取服务器上jar文件的时候就可以通过,如果读取客户端本地磁盘中的文件则不允许访问。代码如下图:
安全检查
如果用户没有指定URLStreamHandler实例,则通过protocol协议来决定使用哪个协议的URLStreamHandler的实例。代码如下:
Paste_Image.png
jdk的sun.net.www.protocol包中默认支持以下几种协议,当然用户也可以扩展URLStreamHandler实例,来实现自定义的协议。
java中默认支持的协议如下图:
Paste_Image.png
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);
}
可以通过URL对象设置的代理来获取URLConnection对象。
public final InputStream openStream() throws java.io.IOException {
return openConnection().getInputStream();
}
直接获取URL指定资源的流InputStream对象,该方法无法向URL中写如数据,也无法访问这个协议的所有的元数据(如:html协议的请求头)。
public final Object getContent() throws java.io.IOException {
return openConnection().getContent();
}
getContent() 方法返回由URL引用的数据,尝试由它建立某种类型的对象。如果URL指定的资源是文本类型(如:html、asciii 文件),返回的就是InputStream对象。如果URL指定的资源是图片则返回java.awt.ImageProducer对象。
final Object getContent(Class[] classes) throws java.io.IOException {
return openConnection().getContent(classes);
}
该方法允许用户选择希望将内容作为那个类型返回。
例如,如果首先将HTML文件作为一个String返回,而第二个选择是Reader,第三个选择是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);
URL类的equals方法是调用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指向的主机、端口、文件路径和片段标识。当所有的都一样才会返回true,但equals方法也会尝试解析DNS,来判断两个主机是否相同。如:可以判断http://localhost:8080/index.html 和 http://127.0.0.1:8080/index.html 两个URL是相等的。
equals方法底层是调用了sameFile()方法。
注意: 因为equals方法有解析DNS的功能,解析DNS是一个阻塞IO操作!所以应当避免URL存储在依赖equals()的数据结构中,如HashMap。如果要存储最后是使用URI来进行存储。URI的equals方法是不会解析DNS的。
该方法作用和equals基本相同,这里也包括DNS解析,不过sameFile()不考虑片段标识问题。下面通过代码来解析sameFIle的实现。
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;
}
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;
}
该方法使用两个URL的host来构造InetAddress对象,调用InetAddress对象的getHost() 方法 来判断两个host是否一致。
InetAddress.getHost()可以通过DNS解析域名,来获取域名绑定的ip地址。