1. 概述
在本文中,我们将探讨Java网络编程的低级操作。我们将深入研究URL。
URL是对网络资源的引用或地址。简单地说,通过网络通信的Java代码可以使用java.net.URL类来表示资源的地址。
Java平台附带了内置的网络支持,捆绑在java.net包中:
import java.net.*;
2. 创建URL
让我们首先创建一个java.net.URL对象,方法是使用它的构造函数并传入一个表示资源的人类可读地址的字符串:
URL url = new URL("/a-guide-to-java-sockets");
我们创建了一个绝对URL对象。该地址包含到达所需资源所需的所有部分。
我们还可以创建一个相对URL;假设我们有代表Tuyucheng主页的URL对象:
URL home = new URL("http://tuyucheng.com");
接下来,让我们创建一个指向我们已知资源的新URL;我们将使用另一个构造函数,它接收一个现有的URL和一个相对于该URL的资源名称:
URL url = new URL(home, "a-guide-to-java-sockets");
我们现在已经创建了一个相对于home的新URL对象url;因此相对URL仅在基本URL的上下文中有效。
我们可以在测试中看到这一点:
@Test
public void givenBaseUrl_whenCreatesRelativeUrl_thenCorrect() {
URL baseUrl = new URL("http://tuyucheng.com");
URL relativeUrl = new URL(baseUrl, "a-guide-to-java-sockets");
assertEquals("http://tuyucheng.com/a-guide-to-java-sockets", relativeUrl.toString());
}
但是,如果检测到相对URL在其组成部分中是绝对的,则忽略baseURL:
@Test
public void givenAbsoluteUrl_whenIgnoresBaseUrl_thenCorrect() {
URL baseUrl = new URL("http://tuyucheng.com");
URL relativeUrl = new URL(baseUrl, "/a-guide-to-java-sockets");
assertEquals("http://tuyucheng.com/a-guide-to-java-sockets", relativeUrl.toString());
}
最后,我们可以通过调用另一个接收URL字符串的组成部分的构造函数来创建URL。我们将在介绍URL组件后的下一节中介绍这一点。
3. URL组件
一个URL由几个部分组成-我们将在本节中探讨这些部分。
让我们首先看一下协议标识符和资源之间的分隔-这两个组件由一个冒号和两个正斜杠分隔,即://。
如果我们有一个URL,例如http://tuyucheng.com,那么分隔符之前的部分http是协议标识符,而后面的部分是资源名称tuyucheng.com。
让我们看一下URL对象公开的API。
3.1 协议
要检索协议-我们使用getProtocol()方法:
@Test
public void givenUrl_whenCanIdentifyProtocol_thenCorrect(){
URL url = new URL("http://tuyucheng.com");
assertEquals("http", url.getProtocol());
}
3.2 端口
要获取端口-我们使用getPort()方法:
@Test
public void givenUrl_whenGetsDefaultPort_thenCorrect(){
URL url = new URL("http://tuyucheng.com");
assertEquals(-1, url.getPort());
assertEquals(80, url.getDefaultPort());
}
请注意,此方法检索显式定义的端口。如果没有显式定义端口,它将返回-1。
并且因为HTTP通信默认使用端口80-因此未定义端口。
下面是一个示例,其中我们确实有一个显式定义的端口:
@Test
public void givenUrl_whenGetsPort_thenCorrect(){
URL url = new URL("http://tuyucheng.com:8090");
assertEquals(8090, url.getPort());
}
3.3 主机
主机是资源名称的一部分,紧跟在://分隔符之后,以域名扩展名结束,在我们的例子中是.com。
我们调用getHost()方法来检索主机名:
@Test
public void givenUrl_whenCanGetHost_thenCorrect(){
URL url = new URL("http://tuyucheng.com");
assertEquals("tuyucheng.com", url.getHost());
}
3.4 文件名
URL中主机名之后的任何内容都称为资源的文件名。它可以包含路径和查询参数,也可以只包含文件名:
@Test
public void givenUrl_whenCanGetFileName_thenCorrect1() {
URL url = new URL("http://tuyucheng.com/guidelines.txt");
assertEquals("/guidelines.txt", url.getFile());
}
假设Tuyucheng在URL /articles?topic=java&version=8下有Java 8文章。主机名之后的所有内容都是文件名:
@Test
public void givenUrl_whenCanGetFileName_thenCorrect2() {
URL url = new URL("http://tuyucheng.com/articles?topic=java&version=8");
assertEquals("/articles?topic=java&version=8", url.getFile());
}
3.5 路径参数
我们也可以只检查路径参数,在我们的例子中是/articles:
@Test
public void givenUrl_whenCanGetPathParams_thenCorrect() {
URL url = new URL("http://tuyucheng.com/articles?topic=java&version=8");
assertEquals("/articles", url.getPath());
}
3.6 查询参数
同样,我们可以检索查询参数,即topic=java&version=8:
@Test
public void givenUrl_whenCanGetQueryParams_thenCorrect() {
URL url = new URL("http://tuyucheng.com/articles?topic=java<em>&version=8</em>");
assertEquals("topic=java<em>&version=8</em>", url.getQuery());
}
4. 使用组件创建URL
现在我们已经了解了不同的URL组件及其在形成资源的完整地址中的位置,我们可以查看另一种通过传入组件部分来创建URL对象的方法。
4.1 使用URL构造函数
第一个构造函数分别接收协议、主机名和文件名:
@Test
public void givenUrlComponents_whenConstructsCompleteUrl_thenCorrect() {
String protocol = "http";
String host = "tuyucheng.com";
String file = "/guidelines.txt";
URL url = new URL(protocol, host, file);
assertEquals("http://tuyucheng.com/guidelines.txt", url.toString());
}
请记住文件名在这种情况下的含义,以下测试应该会更清楚:
@Test
public void givenUrlComponents_whenConstructsCompleteUrl_thenCorrect2() {
String protocol = "http";
String host = "tuyucheng.com";
String file = "/articles?topic=java&version=8";
URL url = new URL(protocol, host, file);
assertEquals("http://tuyucheng.com/articles?topic=java&version=8", url.toString());
}
第二个构造函数分别接收协议、主机名、端口号和文件名:
@Test
public void givenUrlComponentsWithPort_whenConstructsCompleteUrl_thenCorrect() {
String protocol = "http";
String host = "tuyucheng.com";
int port = 9000;
String file = "/guidelines.txt";
URL url = new URL(protocol, host, port, file);
assertEquals("http://tuyucheng.com:9000/guidelines.txt", url.toString());
}
4.2 使用Apache HttpClient的URIBuilder
我们已经了解了如何使用标准的URL()构造函数构建URL对象。但是,当URL包含许多查询参数时,它可能容易出错。
一些流行的框架和库提供了很好的URL构建器,使我们能够轻松地构建URL对象。
首先,让我们看一下Apache HttpClient。
Apache HttpClient是一个流行的库,可以帮助我们处理HTTP请求和响应。此外,它附带了一个URIBuilder,允许我们方便地构建URL对象。
要使用URIBuilder类,我们首先需要将Apache HttpClient依赖项添加到我们的项目中:
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.14</version>
</dependency>
最新版本可以在这里找到。
使用URIBuilder,我们可以轻松设置不同的URL组件,如端口、路径、参数等,并构建URL对象:
@Test
public void givenUrlParameters_whenBuildUrlWithURIBuilder_thenSuccess() throws URISyntaxException, MalformedURLException {
URIBuilder uriBuilder = new URIBuilder("http://tuyucheng.com/articles");
uriBuilder.setPort(9090);
uriBuilder.addParameter("topic", "java");
uriBuilder.addParameter("version", "8");
URL url = uriBuilder.build().toURL();
assertEquals("http://tuyucheng.com:9090/articles?topic=java&version=8", url.toString());
}
正如我们在上面的测试中看到的,我们可以使用URIBuilder的addParameter()方法添加参数。此外,URIBuilder提供了addParameters()方法,它允许我们一次添加多个参数。当我们在特殊数据结构(例如Map)中准备好所有参数时,它特别有用:
@Test
public void givenUrlParametersInMap_whenBuildUrlWithURIBuilder_thenSuccess() throws URISyntaxException, MalformedURLException {
Map<String, String> paramMap = ImmutableMap.of("topic", "java", "version", "8");
URIBuilder uriBuilder = new URIBuilder("http://tuyucheng.com/articles");
uriBuilder.setPort(9090);
uriBuilder.addParameters(paramMap.entrySet()
.stream()
.map(entry -> new BasicNameValuePair(entry.getKey(), entry.getValue()))
.collect(toList()));
URL url = uriBuilder.build().toURL();
assertEquals("http://tuyucheng.com:9090/articles?topic=java&version=8", url.toString());
}
在上面的示例中,我们使用Java Stream API将参数Map转换为BasicNameValuePair对象列表。
4.3 使用Spring Web的UriComponentsBuilder
Spring是一个广泛使用的框架。Spring Web为我们提供了UriComponentsBuilder,这是一个可以直接构建URL对象的工具类。
同样,首先我们必须确保我们的项目包含spring-web依赖项:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>6.0.8</version>
</dependency>
我们可以在Maven中央仓库中找到spring-web的最新版本。
最后,让我们使用UriComponentsBuilder构建一个URL对象:
@Test
public void givenUrlParameters_whenBuildUrlWithSpringUriComponentsBuilder_thenSuccess() throws MalformedURLException {
URL url = UriComponentsBuilder.newInstance()
.scheme("http")
.host("tuyucheng.com")
.port(9090)
.path("articles")
.queryParam("topic", "java")
.queryParam("version", "8")
.build()
.toUri()
.toURL();
assertEquals("http://tuyucheng.com:9090/articles?topic=java&version=8", url.toString());
}
5. 总结
在本教程中,我们介绍了URL类并展示了如何在Java中使用它以编程方式访问网络资源。
与往常一样,本教程的完整源代码可在GitHub上获得。