ApacheHttpClient github地址:https://github.com/apache/httpcomponents-client/tree/4.5.x
ApacheHttpClient官网地址:https://hc.apache.org/httpcomponents-client-4.5.x/index.html
ApacheHttpClient介绍
本文主要想聊一聊ApacheHttpClient中资源释放的那些事,如果对它不够了解的话可以参考下面的思维导图:
链接:https://www.processon.com/view/link/641ef425fc0140496f0a3961
资源类型分类
在聊这个话题前我们先要明确HttpClient中定义的资源是什么,在HttpClient中需要释放的资源可以分为四种
-
内存,应用层用于发送请求或接收响应
-
底层Sokcet
-
Http连接,一个Http连接对应了一块用于收发数据的内存,同时绑定了一个socket。如下所示:
-
Http连接池
收发数据的内存跟socket是绑定在连接对象上的,所以如果我们正常关闭了连接,那么也就完成了对这两种资源的释放
Http连接池通常我们不会关闭,如果关闭了Http连接池意味着整个HttpClient对象不能再被使用
资源释放API介绍
我们常用的资源释放的API可以分为下面几类
-
HttpRequestBase对象的
releaseConnection
跟abort
方法,HttpGet、HttpPost都继承了这个类。源码如下:
这两个方法最终都会调用cancellableRef引用的对象的cancel方法,cancellableRef在不同的时机对应不同的资源对象:ConnectionRequest,代表一个从连接池中获取连接的请求。ConnectionHolder,代表一个真实的http连接
代码位于:
org.apache.http.impl.execchain.MainClientExec#execute
当调用ConnectionRequest的cancel方法时,代表取消本次获取连接的请求。当调用ConnectionHolder的cancel方法时,代表什么呢?代码如下:
org.apache.http.impl.execchain.ConnectionHolder#abortConnection
上面代码主要做了两件事:关闭socket连接,归还连接到连接池。可能有的小伙伴会有疑问,为什么把连接绑定的socket关闭了还要将连接归还到连接池呢?这是因为连接池中的连接数量是有限制的,假设我们设置设置了最大连接数为10,如果不把这个连接归还到连接池,那么它会一直占用一个连接名额直到被驱逐策略所驱逐。并且在调用连接池的releaseConnection方法时,会判断归还的连接是否可用,如果不可用会直接从连接池中移除这个对象
代码:
org.apache.http.impl.conn.PoolingHttpClientConnectionManager#releaseConnection
-
CloseableHttpResponse对象的
close
方法最终会调用
org.apache.http.impl.conn.PoolingHttpClientConnectionManager#releaseConnection
可以看到,当调用response对象的close方法,会关闭socket,同时释放连接到连接池。这里顺便说一下,连接的close方法跟shutdown方法的区别在于,close方法会尝试将未写完的数据全部写入server端,而shutdown会直接丢弃
-
EntityUtils的
consume
方法跟toString方法
以及关闭HttpEntity中的流EntityUtils的consume、toString最终都是调用HttpEntity.getContent().close方法,实际都是在关闭HttpEntity中的流
最终会调用:
org.apache.http.impl.execchain.ResponseEntityProxy#streamClosed
wrapped.close()
最终会调用org.apache.http.impl.io.ContentLengthInputStream#close
,代码如下:可以看到,它只是简单的读取数据但没有进行其它任何操作,这实际是为了释放我们在前文提到了内存。这是为了安全的复用连接,我们需要将上次请求接收并缓存的所有数据清理干净,否则下次使用这个连接时可能读取到上次请求没有读取完的数据
-
CloseableHttpClient对象的
close
方法这个方法会关闭所有HttpClient对象涉及的资源,包括连接池,HttpClient对象建议全局共享一个,因此一般不会调用
正确的释放资源
通过上面的分析,正确的资源释放姿势如下:
private static final CloseableHttpClient httpclient = HttpClients.createDefault();
public static void main(String[] args) throws Exception {
try {
HttpGet httpGet = new HttpGet("http://httpbin.org/get");
CloseableHttpResponse response = httpclient.execute(httpGet);
try {
HttpEntity entity1 = response.getEntity();
// 确保内存完成释放
final String json = EntityUtils.toString(entity1);
// 完成业务处理....
}catch(){
// 异常处理....
}
finally {
// 保证连接释放,如果EntityUtils.toString调用成功,这个方法实际上不会做任何操作
response.close();
}
}