1. 概述

本文将深入探讨Java网络编程中的底层操作,重点聚焦URL的处理。URL是网络资源的地址或引用,Java代码可通过java.net.URL类表示这些资源地址。

Java平台内置网络支持,封装在java.net包中:

import java.net.*;

2. 创建URL

从Java 20开始,java.net.URL的所有构造方法已被弃用,推荐使用java.net.URI构造URL。

通过URI.toURL()创建URL对象:

URL home = new URI("http://baeldung.com/a-guide-to-java-sockets").toURL();

这创建了一个绝对URL对象,包含访问资源的完整信息。

也可以创建相对URI对象,但需注意:toURL()方法禁止将非绝对URI转换为URL:

@Test
public void givenRelativeUrl_whenCreatesRelativeUrl_thenThrows() {
    URI uri = new URI("/a-guide-to-java-sockets");
    Assert.assertThrows(IllegalArgumentException.class, () -> uri.toURL());
}

其他创建方式需先了解URL组件(见下节)。

3. URL组件剖析

URL由多个组件构成,本节逐一解析。

3.1 协议标识符

协议与资源名通过://分隔。例如http://baeldung.com中:

  • http是协议标识符
  • baeldung.com是资源名

获取协议:

@Test
public void givenUrl_whenCanIdentifyProtocol_thenCorrect(){
    URL url = new URI("http://baeldung.com").toURL();
    assertEquals("http", url.getProtocol());
}

3.2 端口

获取端口信息:

@Test
public void givenUrl_whenGetsDefaultPort_thenCorrect(){
    URL url = new URI("http://baeldung.com").toURL();
    assertEquals(-1, url.getPort());       // 未显式指定返回-1
    assertEquals(80, url.getDefaultPort()); // HTTP默认端口80
}

显式指定端口示例:

@Test
public void givenUrl_whenGetsPort_thenCorrect(){
    URL url = new URI("http://baeldung.com:8090").toURL();
    assertEquals(8090, url.getPort());
}

3.3 主机名

主机名是://后到域名扩展前的部分(如.com):

@Test
public void givenUrl_whenCanGetHost_thenCorrect(){
    URL url = new URI("http://baeldung.com").toURL();
    assertEquals("baeldung.com", url.getHost());
}

3.4 文件名

主机名后的部分统称文件名,包含路径和查询参数:

@Test
public void givenUrl_whenCanGetFileName_thenCorrect1() {
    URL url = new URI("http://baeldung.com/guidelines.txt").toURL();
    assertEquals("/guidelines.txt", url.getFile());
}

@Test
public void givenUrl_whenCanGetFileName_thenCorrect2() {
    URL url = new URI("http://baeldung.com/articles?topic=java&version=8").toURL();
    assertEquals("/articles?topic=java&version=8", url.getFile());
}

3.5 路径参数

仅获取路径部分(不含查询参数):

@Test
public void givenUrl_whenCanGetPathParams_thenCorrect() {
    URL url = new URI("http://baeldung.com/articles?topic=java&version=8").toURL();
    assertEquals("/articles", url.getPath());
}

3.6 查询参数

获取查询字符串:

@Test
public void givenUrl_whenCanGetQueryParams_thenCorrect() {
    URL url = new URI("http://baeldung.com/articles?topic=java&version=8").toURL();
    assertEquals("topic=java&version=8", url.getQuery());
}

4. 通过组件构建URL

掌握URL组件后,可通过组件部分构建URL对象。

4.1 使用URI构造器

通过协议、主机、文件名和片段构建:

@Test
public void givenUrlComponents_whenConstructsCompleteUrl_thenCorrect() {
    String protocol = "http";
    String host = "baeldung.com";
    String file = "/guidelines.txt";
    String fragment = "myImage";
    URL url = new URI(protocol, host, file, fragment).toURL();
    assertEquals("http://baeldung.com/guidelines.txt#myImage", url.toString());
}

包含用户信息、端口和查询参数的完整构造:

@Test
public void givenUrlComponents_whenConstructsCompleteUrl_thenCorrect2() {
    String protocol = "http";
    String username = "admin";
    String host = "baeldung.com";
    String file = "/articles";
    String query = "topic=java&version=8";
    String fragment = "myImage";
    URL url = new URI(protocol, username, host, -1, file, query, fragment).toURL();
    assertEquals("http://admin@baeldung.com/articles?topic=java&version=8#myImage", url.toString());
}

4.2 使用Apache HttpClient的URIBuilder

当URL含多个查询参数时,手动构建易出错。推荐使用URIBuilder(需添加依赖):

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.14</version>
</dependency>

链式调用构建URL:

@Test
public void givenUrlParameters_whenBuildUrlWithURIBuilder_thenSuccess() throws URISyntaxException, MalformedURLException {
    URIBuilder uriBuilder = new URIBuilder("http://baeldung.com/articles");
    uriBuilder.setPort(9090);
    uriBuilder.addParameter("topic", "java");
    uriBuilder.addParameter("version", "8");
    URL url = uriBuilder.build().toURL();
    assertEquals("http://baeldung.com:9090/articles?topic=java&version=8", url.toString());
}

批量添加参数(从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://baeldung.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://baeldung.com:9090/articles?topic=java&version=8", url.toString());
}

4.3 使用Spring-web的UriComponentsBuilder

Spring框架提供UriComponentsBuilder(需依赖):

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
    <version>6.0.8</version>
</dependency>

构建示例:

@Test
public void givenUrlParameters_whenBuildUrlWithSpringUriComponentsBuilder_thenSuccess() 
  throws MalformedURLException {
    URL url = UriComponentsBuilder.newInstance()
      .scheme("http")
      .host("baeldung.com")
      .port(9090)
      .path("articles")
      .queryParam("topic", "java")
      .queryParam("version", "8")
      .build()
      .toUri()
      .toURL();
    
    assertEquals("http://baeldung.com:9090/articles?topic=java&version=8", url.toString());
}

5. 总结

本文系统讲解了Java中URL类的使用,包括:

  • ✅ URL对象创建方式
  • ✅ 核心组件解析(协议/端口/主机/路径/查询参数)
  • ✅ 多种构建URL的实践方案(标准API/Apache HttpClient/Spring)

⚠️ 踩坑提示:Java 20后URL构造器已弃用,务必通过URI创建。复杂URL建议使用Apache或Spring的构建器,避免手动拼接错误。

完整代码示例见GitHub仓库


原始标题:A Simple Guide to the Java URL