认识 Java 中的 URL

简介: 什么是URL?URL 即 Uniform Resource Locator,翻译为中文为统一资源定位符,表示万维网上的一个资源,资源可以是实际存在的一个文件,也可以是抽象的数据库的查询结果。

什么是URL?


URL 即 Uniform Resource Locator,翻译为中文为统一资源定位符,表示万维网上的一个资源,资源可以是实际存在的一个文件,也可以是抽象的数据库的查询结果。


可以使用特定的字符串来表示这种资源,字符串格式为:protocol:userInfo@host:port/path?query#ref,如http://zzuhkp.com/index.html?key=abc#section1,具体含义如下:


protocol:访问资源的协议,如 http、ftp、file。

userInfo:访问资源的用户信息,该部分可选的,用户信息可能包含用户名和密码。

host:资源所在的主机,可以是 IP 地址或者主机名。

port:访问资源时使用的端口号,如果不存在,将使用协议默认的端口号。

path:资源所在的路径。

query:访问资源提供的附加查询参数,一般为键值对,键和值使用等号连接,多个键值对使用&连接,如果是中文还需要转为ASCII表示的形式,如 a=1&b=2。

ref:表示资源中的特定部分,如 html 页面中的锚点(Anchor)。

Java 中 URL 的表示

Java 提供了 java.net.URL 类表示统一资源定位符,统一资源定位符的目的是获取资源,因此 java.net.URL 同样也提供了获取资源的方法。


如何实例化 URL


先看如何实例化 URL,Java 提供了 URL 的几个构造方法用于创建 URL 对象的实例,具体如下。

  /**
     * @param      protocol   使用的协议名称
     * @param      host       主机名称
     * @param      port       主机的端口号
     * @param      file       主机上的资源,即 path
     * @param      handler    URL 的流处理器
     */
    public URL(String protocol, String host, int port, String file) throws MalformedURLException
  public URL(String protocol, String host, String file) throws MalformedURLException
  public URL(String protocol, String host, int port, String file,URLStreamHandler handler) throws MalformedURLException
  /** 
   * @param      context   URL字符串的上下文
     * @param      spec      待解析的URL字符串,可能是一个相对路径
     * @param      handler   URL的流处理器
     */
  public URL(String spec) throws MalformedURLException
  public URL(URL context, String spec) throws MalformedURLException
  public URL(URL context, String spec, URLStreamHandler handler) throws MalformedURLException


构造方法可以分为两类,一类构造方法参数是组成 URL 字符串的各部分,另一类构造方法参数是URL 字符串。从构造方法参数中我们还可以看到有一个类型为 URLStreamHandler 的参数,它和获取 URL 对应资源的流有关系。


如何获取 URL 的基本信息


创建 URL 实例对象时无论是直接提供组成 URL 字符串的各部分,还是提供完整的 URL 字符串,Java 中的 URL 对象都会在底层存储表示 URL 的各部分,并且提供了一些方法用于获取相关信息,具体如下。


public String getProtocol()
public String getHost()
public int getPort()
public String getFile()
public String getQuery()
public String getAuthority()
public String getPath()
public String getUserInfo()
public String getRef()
public int getDefaultPort()


多数上述方法我们可以直接看出其获取的信息,而 authority 表示的是host:port,defaultPort 表示的是协议的默认端口。


如何获取 URL 表示的资源


URL 生来就是用来表示资源,因此 Java 中的 URL 也提供了一些方法用于获取资源内容,在 Java 中可以使用流来表示。URL 中获取资源的方法如下。


// 获取 URL 连接,连接对象可以获取更多的资源信息,如内容类型、内容长度、最后修改时间等。
public URLConnection openConnection() throws java.io.IOException
// 通过代理获取 URL 连接
public URLConnection openConnection(Proxy proxy) throws java.io.IOException
// 获取 URL 表示资源对应的输入流
public final InputStream openStream() throws java.io.IOException        


可以看到,获取资源有两种方式,一种是通过 openConnection 方法先获取 URLConnection ,然后通过 URLConnection 获取到更多的资源信息,另一种方式是直接使用 openStream 获取表示 URL 资源的输入流,其底层调用 openConnection 方法进行实现。


例如,如果我们想获取到百度首页的信息,我们可以使用如下的代码。


public class App {
    public static void main(String[] args) throws IOException {
        String spec = "https://www.baidu.com/";
        URL url = new URL(spec);
        URLConnection urlConnection = url.openConnection();
        urlConnection.connect();
        InputStream inputStream = urlConnection.getInputStream();
        // 获取资源内容
        String content = IoUtil.readUtf8(inputStream);
        System.out.println(content);
        inputStream.close();
    }
}


URL 扩展自定义协议


URL 可以表示各种协议的资源,那是不是所有的协议的资源都可以通过 URL 来获取呢?我们可以直接自定义一个 x 协议,然后通过 URL 获取吗?


答案是否定的,我们可以直接获取到 http 协议的资源内容,是因为 Java 中内置了对 http 协议资源的处理,处理的类就是 URLStreamHandler ,这个对象可以在调用构造方法实例化 URL 时提供。如果调用了不提供 URLStreamHandler 参数的构造方法,则需要使用一定的策略获取 URLStreamHandler 。


URL 资源获取实现分析


下面来分析,如何获取自定义协议的资源。跟踪 openConnection 方法的源码,具体如下。


public final class URL implements java.io.Serializable {
    transient URLStreamHandler handler;
    public URLConnection openConnection() throws java.io.IOException {
        return handler.openConnection(this);
    }
}


我们看到,URLConnection 是通过 handler 来获取的,handler 是 URL 类中的成员变量, 其在构造方法执行的时候会进行实例化,具体如下。


public final class URL implements java.io.Serializable {
    public URL(String protocol, String host, int port, String file,
               URLStreamHandler handler) throws MalformedURLException {
    ... 省略部分代码
        if (handler == null &&
            (handler = getURLStreamHandler(protocol)) == null) {
            throw new MalformedURLException("unknown protocol: " + protocol);
        }
        this.handler = handler;
    }
}


构造方法执行时为初始化 URLStreamHandler ,其调用了 getURLStreamHandler 方法根据协议来获取,继续跟踪源码。


public final class URL implements java.io.Serializable {
  static URLStreamHandlerFactory factory;
    private static final String protocolPathProp = "java.protocol.handler.pkgs";
    static Hashtable<String,URLStreamHandler> handlers = new Hashtable<>();
    private static Object streamHandlerLock = new Object();
    static URLStreamHandler getURLStreamHandler(String protocol) {
    // 优先从缓存中获取 URLStreamHandler 
        URLStreamHandler handler = handlers.get(protocol);
        if (handler == null) {
            boolean checkedWithFactory = false;
            if (factory != null) {
              // 缓存不存在,尝试从工厂中获取 URLStreamHandler 
                handler = factory.createURLStreamHandler(protocol);
                checkedWithFactory = true;
            }
      // 工厂不存在或者未获取到 URLStreamHandler ,继续尝试获取
            if (handler == null) {
                String packagePrefixList = null;
        // 从 Java 启动参数中获取 URLStreamHandler 类所在的包名 
                packagePrefixList
                    = java.security.AccessController.doPrivileged(
                    new sun.security.action.GetPropertyAction(
                        protocolPathProp,""));
                if (packagePrefixList != "") {
                    packagePrefixList += "|";
                }
              // 启动参数优先,另外加上特定的包名
                packagePrefixList += "sun.net.www.protocol";
                StringTokenizer packagePrefixIter =
                    new StringTokenizer(packagePrefixList, "|");
                while (handler == null &&
                       packagePrefixIter.hasMoreTokens()) {
                    // 循环从包名列表中获取 URLStreamHandler 的实例   
                    String packagePrefix =
                      packagePrefixIter.nextToken().trim();
                    try {
                        String clsName = packagePrefix + "." + protocol +
                          ".Handler";
                        Class<?> cls = null;
                        try {
                            cls = Class.forName(clsName);
                        } catch (ClassNotFoundException e) {
                            ClassLoader cl = ClassLoader.getSystemClassLoader();
                            if (cl != null) {
                                cls = cl.loadClass(clsName);
                            }
                        }
                        if (cls != null) {
                            handler  =
                              (URLStreamHandler)cls.newInstance();
                        }
                    } catch (Exception e) {
                        // any number of exceptions can get thrown here
                    }
                }
            }
      // 加锁处理多线程安全问题
            synchronized (streamHandlerLock) {
                URLStreamHandler handler2 = null;
                handler2 = handlers.get(protocol);
                if (handler2 != null) {
                    return handler2;
                }
                if (!checkedWithFactory && factory != null) {
                    handler2 = factory.createURLStreamHandler(protocol);
                }
                if (handler2 != null) {
                    handler = handler2;
                }
                if (handler != null) {
                    handlers.put(protocol, handler);
                }
            }
        }
        return handler;
    }
}


根据上述代码,最终是调用了URLStreamHandler#openConnection(URL)获取 URLConnection,然后调用 URLConnection#getInputStream 方法获取表示资源的 InputStream, URLStreamHandler 的获取流程如下。


优先从缓存中获取 URLStreamHandler 。

缓存中不存在,尝试从 URLStreamHandlerFactory 工厂中获取 URLStreamHandler 。

如果工厂不存在或者没有从工厂中获取到 URLStreamHandler 则通过反射从启动命令中 java.protocol.handler.pkgs 参数指定的包名或者sun.net.www.protocol包名中根据命名规则,通过反射获取 URLStreamHandler 的实例。

最后将结果进行缓存。


如何在 URL 中扩展自定义协议


根据上述的分析,我们可以得知具有以下方式使 URL 支持自定义的协议。


实例化 URL 时指定 URLStreamHandler 。

为 URL 设置 URLStreamHandlerFactory ,设置方法为java.net.URL#setURLStreamHandlerFactory,注意该方法只能调用一次,再次调用将抛出异常。

在 Java 启动参数 java.protocol.handler.pkgs 中指定 URLStreamHandler 的包名,多个包名使用|分隔,或者将 URLStreamHandler 类放在包 sun.net.www.protocol 中,注意此时 URLStreamHandler 类的命名规则为 packagePrefix.protocol.Handler。

如果我们想要 URL 支持 x 协议,我们可以自定义 URLStreamHandler 如下。


package sun.net.www.protocol.x;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
/**
 * @author zzuhkp
 * @date 2020-11-16 14:52
 * @since 1.0
 */
public class Handler extends URLStreamHandler {
    @Override
    protected URLConnection openConnection(URL url) throws IOException {
        return new URLConnection(url) {
            @Override
            public void connect() throws IOException {
            }
            @Override
            public InputStream getInputStream() throws IOException {
                return new FileInputStream(url.getPath());
            }
        };
    }
}


测试代码如下。


public class App {
    public static void main(String[] args) throws IOException {
        String spec = "x:D:\\Desktop\\新建文本文档.txt";
        URL url = new URL(spec);
        URLConnection urlConnection = url.openConnection();
        urlConnection.connect();
        InputStream inputStream = urlConnection.getInputStream();
        // 获取资源内容
        String content = IoUtil.readUtf8(inputStream);
        System.out.println(content);
        inputStream.close();
    }
}


总结

本篇先介绍了 URL 的概念,然后介绍了如何在 Java 中获取 URL 中的信息及获取 URL 表示的资源,最后还分析了 URL 获取资源的源码及介绍如何让 URL 支持自定义的协议。URL 作为计算机领域中的基本概念,希望大家都能够掌握,欢迎大家留言讨论。

目录
相关文章
|
1月前
|
Java
Java开发实现图片URL地址检验,如何编码?
【10月更文挑战第14天】Java开发实现图片URL地址检验,如何编码?
69 4
|
26天前
|
Java 程序员
JAVA程序员的进阶之路:掌握URL与URLConnection,轻松玩转网络资源!
在Java编程中,网络资源的获取与处理至关重要。本文介绍了如何使用URL与URLConnection高效、准确地获取网络资源。首先,通过`java.net.URL`类定位网络资源;其次,利用`URLConnection`类实现资源的读取与写入。文章还提供了最佳实践,包括异常处理、连接池、超时设置和请求头与响应头的合理配置,帮助Java程序员提升技能,应对复杂网络编程场景。
49 9
|
26天前
|
人工智能 Java 物联网
JAVA网络编程的未来:URL与URLConnection的无限可能,你准备好了吗?
随着技术的发展和互联网的普及,JAVA网络编程迎来新的机遇。本文通过案例分析,探讨URL与URLConnection在智能API调用和实时数据流处理中的关键作用,展望其未来趋势和潜力。
43 7
|
3月前
|
Java
【思维导图】JAVA网络编程思维升级:URL与URLConnection的逻辑梳理,助你一臂之力!
【思维导图】JAVA网络编程思维升级:URL与URLConnection的逻辑梳理,助你一臂之力!
57 1
|
3月前
|
XML JSON 搜索推荐
【高手过招】JAVA网络编程对决:URL与URLConnection的高级玩法,你敢挑战吗?
【高手过招】JAVA网络编程对决:URL与URLConnection的高级玩法,你敢挑战吗?
71 0
|
26天前
|
Java 开发者
JAVA高手必备:URL与URLConnection,解锁网络资源的终极秘籍!
在Java网络编程中,URL和URLConnection是两大关键技术,能够帮助开发者轻松处理网络资源。本文通过两个案例,深入解析了如何使用URL和URLConnection从网站抓取数据和发送POST请求上传数据,助力你成为真正的JAVA高手。
46 11
|
26天前
|
JSON 安全 算法
JAVA网络编程中的URL与URLConnection:那些你不知道的秘密!
在Java网络编程中,URL与URLConnection是连接网络资源的两大基石。本文通过问题解答形式,揭示了它们的深层秘密,包括特殊字符处理、请求头设置、响应体读取、支持的HTTP方法及性能优化技巧,帮助你掌握高效、安全的网络编程技能。
48 9
|
26天前
|
JSON Java API
JAVA网络编程新纪元:URL与URLConnection的神级运用,你真的会了吗?
本文深入探讨了Java网络编程中URL和URLConnection的高级应用,通过示例代码展示了如何解析URL、发送GET请求并读取响应内容。文章挑战了传统认知,帮助读者更好地理解和运用这两个基础组件,提升网络编程能力。
45 5
|
1月前
|
存储 网络协议 前端开发
在 Java 中如何完全验证 URL
在 Java 中如何完全验证 URL
84 8
|
24天前
|
Java Spring
JAVA获取重定向地址URL的两种方法
【10月更文挑战第17天】本文介绍了两种在Java中获取HTTP响应头中的Location字段的方法:一种是使用HttpURLConnection,另一种是使用Spring的RestTemplate。通过设置连接超时和禁用自动重定向,确保请求按预期执行。此外,还提供了一个自定义的`NoRedirectSimpleClientHttpRequestFactory`类,用于禁用RestTemplate的自动重定向功能。
下一篇
无影云桌面