Java URL类源码剖析

前几天重新把URL厘清了一下(这里), 趁热打铁,把JDK的URL类源码也仔细读了,务求把URL的方方面面都了然于胸。

注:在此用的JDK8的版本。

首先,URL在java.net这个包,类签名如下:
public final class URL implements java.io.Serializable
可见URL是一个final类,即URL类无法被继承,并实现了Serializable接口,即URL对象可被序列化。

再看URL主要的实例属性:

private String protocol; private String host;
private int port; private String file;
private transient String query; private String authority;
private transient String path; private String ref;

容易发现其实它跟URL格式的每一部分一一对应:
scheme:[//[user[:password]@]host[:port]][/path][?query][#fragment]

再看URL类最常用的一个constructor:
public URL(String spec) throws MalformedURLException { this(null, spec); }
public URL(URL context, String spec) throws MalformedURLException { this(context, spec, null); }
它最终会调到下面这个constructor:
public URL(URL context, String spec, URLStreamHandler handler) throws MalformedURLException

那我们就看这个constructor是怎么实现URL的解析的:
它先把头尾的空格去掉,注意,它这里用的判断是<= ' ', 因为在ASCII码中小于空格(32)的都是不可见的字符。
然后判断传入的字符串中是否以url:开头, 如果有,就去掉,所以,如果传入URL字符串是以url:开头的,JDK还是能解析出来的。
接着判断是否相对URL。

然后解析出protocol,方法比较直观,就是第一个:前面的所有字符,并把start变量设置为:后面第一个为开始。这里还验证了一下protocol的格式,验证比较粗糙,只是确保protocol字符串的首字符是字母,并且protocol字符串全部是字母或者数字且不包含'.','+'和'-'。

然后是根据protocol来加载相应的URLStreamHandler:

getURLStreamHandler(protocol)这个方法里核心的代码如下:
可以看到,这里会根据protocol去相应的包里面去加载Handler类,比如,如果protocol是HTTP的,就加载sun.net.www.protocol.http.Handler这个类,并通过反射来new一个实例出来。 接下来就是通过这个Handler来解析和构造这个URL对象了:

这里我们以http协议为例,sun.net.www.protocol.http.Handler这个类是继承java.net.URLStreamHandler,parseURL方法调用的就是URLStreamHandler里面实现的:

先解析出spec,这个spec里面除去了Query查询参数部分,只包含Authority,Host,Port和Path部分: 接着就是解析出Authority部分,也就是用户和密码: 然后在解析出Path:

最后再调用setURL方法把各部分的值设置好:

setURL方法最后会调用set方法如下:

这里值得注意的是file属性,它设置的是:
this.file = query == null ? path : path + "?" + query;
就是说如果在URL里面指定了query查询参数,那么file就是path?query,如果没有,就是path。我这里为什么要着重强调了这个是因为,这个file属性才是HTTP 请求行中的Path! 如:
GET /hello.jsp?name=Eric HTTP/1.1

URL类源码分析到此为止,接下来我会继续分析Java中如何通过一个URL实例来打开一个connection,然后发送请求的整个过程。

Written on 15 May 2017

Read more on Java