Java 中发起 HTTP 请求的 5 种方式

更新于 2025-12-27

Matthew Gilliard 2020-07-21

发起 HTTP 请求是现代编程的核心功能之一,通常也是学习一门新语言时最先尝试的事情。对于 Java 开发者而言,有多种方式可以实现——既可以使用 JDK 自带的核心库,也可以借助第三方库。本文将介绍我常用的 Java HTTP 客户端。如果你使用的是其他客户端,那也很好!欢迎告诉我。

在本文中,我会涵盖以下内容:

  • 核心 Java 库
    • HttpURLConnection
    • HttpClient(Java 11+)
  • 流行第三方库
    • Apache HttpClient
    • OkHttp
    • Retrofit

所有示例代码均基于 Java 11,并调用 NASA 提供的「每日天文图」(Astronomy Picture of the Day, APOD)API。完整代码已托管在 GitHub 上。


使用 Java 核心 API 发起 HTTP 请求

自 Java 1.1 起,JDK 就内置了 HTTP 客户端。而在 Java 11 中,又新增了一个更现代化的客户端。如果你希望避免引入额外依赖,这两个选项可能很适合你。

Java 1.1 的 HttpURLConnection

首先,我们得面对一个哲学问题:类名中的缩写到底要不要大写?先别纠结了。闭上眼睛,回到 1997 年——《泰坦尼克号》正在横扫票房并催生无数表情包,辣妹组合(Spice Girls)发行了畅销专辑,但当年最轰动的大事,无疑是 HttpURLConnection 被加入 Java 1.1。

下面是如何使用它向 APOD API 发起 GET 请求的示例:

// 创建一个 URL 对象
URL url = new URL("https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY");

// 打开连接并强制转换为 HttpURLConnection
HttpURLConnection connection = (HttpURLConnection) url.openConnection();

// 设置请求方法、请求头等(此时连接已“打开”)
connection.setRequestProperty("accept", "application/json");

// 这一行真正发起请求
InputStream responseStream = connection.getInputStream();

// 使用 Jackson 手动将响应体 InputStream 转换为 APOD 对象
ObjectMapper mapper = new ObjectMapper();
APOD apod = mapper.readValue(responseStream, APOD.class);

// 最终获得响应数据
System.out.println(apod.title);

这段代码显得相当冗长,而且操作顺序令人困惑(为什么要在打开连接之后才设置请求头?)。虽然它支持 POST 请求体、自定义超时等复杂场景,但我始终觉得这个 API 不够直观。

那么,什么时候该用 HttpURLConnection 呢?如果你需要支持使用旧版 Java 的客户端,且无法添加外部依赖,那它可能是唯一选择。不过我相信这类开发者已经很少了,更多时候你可能会在老旧代码库中见到它。如需更现代的方案,请继续阅读。


Java 11 的 HttpClient

时隔二十多年,在《黑豹》热映的 2018 年,Java 11 终于引入了全新的 java.net.http.HttpClient。它拥有更合理的 API 设计,支持 HTTP/2 和 WebSocket,并且可以选择同步或异步方式发起请求(通过 CompletableFuture)。

通常,发起 HTTP 请求后我们最关心的是如何将响应体解析为对象。如果某个库在这方面做得不好,那它很难让我感到愉悦。HttpClient 通过 BodyHandler 将 HTTP 响应转换为你指定的类型。JDK 内置了一些处理器,比如 Stringbyte[](用于二进制数据)、按行分割的 Stream<String> 等。但没有内置的 JSON 解析器,因此我基于 Jackson 编写了一个自定义的 JsonBodyHandler参考实现),它返回一个 Supplier<APOD>,使用时调用 .get() 即可。

同步请求示例:

// 创建客户端
var client = HttpClient.newHttpClient();

// 构建请求
var request = HttpRequest.newBuilder(
        URI.create("https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY"))
    .header("accept", "application/json")
    .build();

// 发送请求并处理响应
var response = client.send(request, new JsonBodyHandler<>(APOD.class));

// 输出结果
System.out.println(response.body().get().title);

异步请求示例:

// 异步发送请求
var responseFuture = client.sendAsync(request, new JsonBodyHandler<>(APOD.class));

// 在此期间可以执行其他任务

// 阻塞等待结果(实际使用中建议用 thenApply 等非阻塞方式)
var response = responseFuture.get();
System.out.println(response.body().get().title);

第三方 Java HTTP 客户端库

如果内置客户端不能满足需求,别担心!还有许多优秀的第三方库可供选择。

Apache HttpClient

Apache 软件基金会的 HTTP 客户端历史悠久、广泛使用,也是许多高层库的基础。需要注意的是,其历史有些混乱:旧版的 Commons HttpClient 已停止维护,新版(仍叫 HttpClient)属于 HttpComponents 项目。2020 年初发布的 5.0 版本增加了对 HTTP/2 的支持,并同时支持同步与异步请求。

总体而言,它的 API 偏底层——很多功能需要你自己实现。下面的代码调用了 NASA API,看起来不难,但我省略了生产环境中必需的错误处理逻辑,并且仍需手动用 Jackson 解析 JSON。此外,若不配置日志框架,控制台可能会输出烦人的警告(虽无大碍,但看着不舒服)。

ObjectMapper mapper = new ObjectMapper();

try (CloseableHttpClient client = HttpClients.createDefault()) {
    HttpGet request = new HttpGet("https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY");

    APOD response = client.execute(request, httpResponse ->
        mapper.readValue(httpResponse.getEntity().getContent(), APOD.class));

    System.out.println(response.title);
}

OkHttp

OkHttp 是 Square 公司开发的 HTTP 客户端,内置了许多实用功能,例如自动处理 GZIP 压缩、响应缓存、网络错误时自动重试或切换备用主机,以及支持 HTTP/2 和 WebSocket。其 API 简洁明了,但同样没有内置 JSON 解析,因此仍需配合 Jackson:

ObjectMapper mapper = new ObjectMapper();

OkHttpClient client = new OkHttpClient();

Request request = new Request.Builder()
    .url("https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY")
    .build(); // 默认为 GET 请求

Response response = client.newCall(request).execute();

APOD apod = mapper.readValue(response.body().byteStream(), APOD.class);

System.out.println(apod.title);

不过,OkHttp 的真正威力在于与 Retrofit 结合使用。


Retrofit

Retrofit 同样来自 Square,构建于 OkHttp 之上。它在保留 OkHttp 所有底层能力的同时,提供了一种通过注解定义接口的方式,将 HTTP 细节抽象为简洁的 Java API。

首先,定义一个接口来描述 APOD API 的调用方法:

public interface APODClient {
    @GET("/planetary/apod")
    @Headers("accept: application/json")
    CompletableFuture<APOD> getApod(@Query("api_key") String apiKey);
}

返回类型 CompletableFuture<APOD> 表示这是一个异步客户端(Square 也提供了其他适配器,或可自定义)。这种接口形式非常便于单元测试中的 Mock 操作。

接着,让 Retrofit 根据该接口生成实现类,并指定基础 URL:

Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.nasa.gov")
    .addConverterFactory(JacksonConverterFactory.create())
    .build();

APODClient apodClient = retrofit.create(APODClient.class);

CompletableFuture<APOD> response = apodClient.getApod("DEMO_KEY");

// 可在此执行其他任务

APOD apod = response.get();
System.out.println(apod.title);

API 认证处理

如果多个接口方法都需要传递 api_key,可以通过为 OkHttpClient 添加拦截器(Interceptor)统一处理:

private OkHttpClient clientWithApiKey(String apiKey) {
    return new OkHttpClient.Builder()
            .addInterceptor(chain -> {
                Request originalRequest = chain.request();
                HttpUrl newUrl = originalRequest.url().newBuilder()
                    .addQueryParameter("api_key", apiKey).build();
                Request request = originalRequest.newBuilder().url(newUrl).build();
                return chain.proceed(request);
            }).build();
}

我喜欢这种面向接口的 API 设计(除最简单场景外)。用类来表示远程 API 是一种优雅的抽象,能很好地融入依赖注入体系。而 Retrofit 能基于可定制的 OkHttp 客户端自动生成实现,非常强大。


其他 Java HTTP 客户端

文章发布到 Twitter 后,我欣喜地看到大家热烈讨论各自使用的 HTTP 客户端。如果你对上述选项都不满意,不妨看看这些建议:

  • REST Assured:专为测试 REST 服务设计的客户端,提供流畅的链式语法和便捷的响应断言方法。
  • cvurl:对 Java 11 HttpClient 的封装,简化了复杂请求的编写。
  • Feign:与 Retrofit 类似,可通过注解接口生成客户端,高度灵活,支持多种请求/响应处理方式、指标监控、重试机制等。
  • Spring 的 RestTemplate(同步)与 WebClient(异步):如果你的项目已全面采用 Spring 生态,沿用它们是个不错的选择。
  • MicroProfile Rest Client:同样基于“注解接口生成客户端”的理念,独特之处在于可复用同一接口同时构建服务端和客户端,确保两者完全匹配。如果你既要开发服务又要提供客户端,值得考虑。

总结

Java 中的 HTTP 客户端选择非常丰富:

  • 简单场景:推荐使用内置的 java.net.http.HttpClient(Java 11+)。
  • 复杂应用或需要 API 抽象:建议使用 RetrofitFeign