1. 概述
Apache HttpClient 是一个流行的 Java 库,它提供了高效且功能丰富的包,实现了最新HTTP标准的客户端部分。该库旨在易于扩展,同时为基本的HTTP方法提供了强大的支持。
在这篇教程中,我们将探讨Apache HttpClient API的设计。我们将解释HttpClient
与CloseableHttpClient
之间的区别,并了解如何使用HttpClients
或HttpClientBuilder
创建CloseableHttpClient
实例。
最后,我们将推荐在自定义代码中应使用哪种API,并查看哪些API类实现了Closeable
接口,因此需要关闭它们的实例以释放资源。
2. API设计
让我们首先了解API的设计,重点关注其高层类和接口。下图展示了执行经典HTTP请求和处理HTTP响应所需的API部分:
此外,Apache HttpClient API还支持异步HTTP请求/响应交换,以及使用RxJava进行反应式消息交换。
3. HttpClient
vs. CloseableHttpClient
HttpClient
是一个代表HTTP请求执行基本合同的高层接口。它对请求执行过程没有任何限制,而状态管理、身份验证和重定向等具体细节留给各个客户端实现。
我们可以将任何客户端实现转换为HttpClient
接口,从而通过默认客户端实现执行基本HTTP请求:
HttpClient httpClient = HttpClients.createDefault();
HttpGet httpGet = new HttpGet(serviceOneUrl);
httpClient.execute(httpGet, response -> {
assertThat(response.getCode()).isEqualTo(HttpStatus.SC_OK);
return response;
});
请注意execute方法的第二个参数是一个HttpClientResponseHandler
函数式接口。
然而,上述代码会在SonarQube上产生一个阻塞问题。原因在于默认客户端实现返回的是一个CloseableHttpClient
实例,这需要关闭。
CloseableHttpClient
是一个抽象类,代表**HttpClient
接口的基本实现**。但它也实现了Closeable
接口,因此在使用后应关闭所有实例。我们可以通过使用try-with-resources
或在finally
块中调用close
方法来关闭它们:
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
HttpGet httpGet = new HttpGet(serviceOneUrl);
httpClient.execute(httpGet, response -> {
assertThat(response.getCode()).isEqualTo(HttpStatus.SC_OK);
return response;
});
}
因此,在我们的自定义代码中,应该使用CloseableHttpClient
类,而不是HttpClient
接口。
4. HttpClients
vs. HttpClientBuilder
在上面的例子中,我们使用了HttpClients
类的一个静态方法来获取默认的客户端实现。HttpClients
是一个包含用于创建CloseableHttpClient
实例的工厂方法的工具类:
CloseableHttpClient httpClient = HttpClients.createDefault();
我们可以使用HttpClientBuilder
类实现相同的功能。HttpClientBuilder
是用于创建CloseableHttpClient
实例的构建者模式实现:
CloseableHttpClient httpClient = HttpClientBuilder.create().build();
内部地,HttpClients
使用HttpClientBuilder
来创建客户端实现实例。因此,在自定义代码中,我们应该优先使用HttpClients
。鉴于它是更高层次的类,其内部可能会随着新版本的发布而变化。
5. 资源管理
我们需要关闭CloseableHttpClient
实例的原因是在其作用域之外关闭关联的连接管理器。
5.1. 自动资源释放(HttpClient 4.x)
在当前的Apache HttpClient 5版本中,通过我们之前看到的HttpClientResponseHandler
,通信结束后会自动释放客户端资源。
在当前版本之前,为了向HttpClient 4.x提供向后兼容性,提供了CloseableHttpResponse
。
CloseableHttpResponse
是一个实现ClassicHttpResponse
接口的类。然而,ClassicHttpResponse
还扩展了HttpResponse
、HttpEntityContainer
和Closeable
接口。
HTTP连接由响应对象持有,以便可以直接从网络套接字流式传输响应内容。因此,在自定义代码中,我们应该使用CloseableHttpResponse
类而不是HttpResponse
接口。一旦消费完响应,我们也需要确保调用close
方法:
try (CloseableHttpClient httpClient = HttpClientBuilder.create().build()) {
HttpGet httpGet = new HttpGet(serviceUrl);
try (CloseableHttpResponse response = httpClient.execute(httpGet)) {
HttpEntity entity = response.getEntity();
EntityUtils.consume(entity);
}
}
值得注意的是,当响应内容未完全消耗时,底层连接不能安全地重新使用。在这种情况下,连接将被连接管理器关闭并丢弃。
5.2. 重用客户端
每次请求都关闭一个CloseableHttpClient
实例然后再创建新的可能是一个昂贵的操作。相反,我们可以使用单个CloseableHttpClient
实例发送多个请求,以避免频繁关闭和创建连接管理器:
try (CloseableHttpClient httpClient = HttpClientBuilder.create().build()) {
HttpGet httpGetOne = new HttpGet(serviceOneUrl);
httpClient.execute(httpGetOne, responseOne -> {
HttpEntity entityOne = responseOne.getEntity();
EntityUtils.consume(entityOne);
assertThat(responseOne.getCode()).isEqualTo(HttpStatus.SC_OK);
return responseOne;
});
HttpGet httpGetTwo = new HttpGet(serviceTwoUrl);
httpClient.execute(httpGetTwo, responseTwo -> {
HttpEntity entityTwo = httpGetTwo.getEntity();
EntityUtils.consume(entityTwo);
assertThat(responseTwo.getCode()).isEqualTo(HttpStatus.SC_OK);
return responseTwo;
});
}
这样,我们就避免了关闭内部关联的连接管理器并创建一个新的。
6. 总结
在这篇文章中,我们深入探讨了Apache HttpClient的经典HTTP API,这是一个流行的Java客户端HTTP库。
我们学习了HttpClient
与CloseableHttpClient
的区别,并建议在自定义代码中使用CloseableHttpClient
。接下来,我们了解了如何使用HttpClients
或HttpClientBuilder
创建CloseableHttpClient
实例。
最后,我们关注了CloseableHttpClient
和CloseableHttpResponse
类,它们都实现了Closeable
接口。我们了解到,这些实例的关闭对于释放资源至关重要。
如往常一样,完整的源代码可以在GitHub上找到。对于HttpClient 4.x的代码片段,请参阅我们的HttpClient 4模块。