HttpClient連接池及重試機制解析
一、HttpClient
簡介
HttpClient 是Apache Jakarta Common 下的子項目,可以用來提供高效的、最新的、功能豐富的支持 HTTP 協(xié)議的客戶端編程工具包,基于標準的java語言。
功能介紹
- 支持HTTP和HTTPS協(xié)議
- 實現(xiàn)了HTTP的方法,GET,POST,PUT,DELETE等方法。
- 連接管理器支持多線程的應用。
- 可以設置連接超時
使用方法
使用HttpClient發(fā)送請求,接收響應可以分為一下幾步:
- 創(chuàng)建HttpClient對象
- 創(chuàng)建請求方法的實例,并且指定URL
- 發(fā)送請求參數(shù),GET請求和POST請求發(fā)送參數(shù)的方式有所不同
- 調用HttpClient對象的execute方法,返回HttpResponse對象
- 調用HttpResponse的getAllHeaders()、getHeaders(String name)等方法可獲取服務器的響應頭;調用HttpResponse的getEntity()方法可獲取HttpEntity對象,該對象包裝了服務器的響應內容
- 連接釋放。無論成功與否,必須釋放連接
二、HttpClientUtil
2.1 HttpClient版本
筆者用到的版本是4.5.5,由于是maven工程,需要在pom文件引入對應的坐標。
<dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.5</version> </dependency>
2.2 項目中用到的工具類如下
package cn.htjc.customer.util;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.ServiceUnavailableRetryStrategy;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HttpContext;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.util.EntityUtils;
import java.io.IOException;
import java.net.SocketTimeoutException;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@Slf4j
public class HttpClientUtil {
// utf-8字符編碼
private static final String CHARSET_UTF_8 = "utf-8";
// HTTP內容類型。相當于form表單的形式,提交數(shù)據(jù)
private static final String CONTENT_TYPE_FORM_URL = "application/x-www-form-urlencoded";
// 連接管理器
private static PoolingHttpClientConnectionManager pool;
// 請求配置
private static RequestConfig requestConfig;
static {
try {
log.info("初始自定義HttpClient......開始");
SSLContextBuilder builder = new SSLContextBuilder();
builder.loadTrustMaterial(null, new TrustSelfSignedStrategy());
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(builder.build());
// 配置同時支持 HTTP 和 HTPPS
Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", PlainConnectionSocketFactory.getSocketFactory())
.register("https", sslsf).build();
// 初始化連接管理器
pool = new PoolingHttpClientConnectionManager(
socketFactoryRegistry);
// 設置連接池的最大連接數(shù)
pool.setMaxTotal(200);
// 設置每個路由上的默認連接個數(shù)
pool.setDefaultMaxPerRoute(20);
// 根據(jù)默認超時限制初始化requestConfig
// 客戶端從服務器讀取數(shù)據(jù)的timeout
int socketTimeout = 1000;
// 客戶端和服務器建立連接的timeout
int connectTimeout = 10000;
// 從連接池獲取連接的timeout
int connectionRequestTimeout = 10000;
//設置請求超時時間
requestConfig = RequestConfig.custom().setConnectionRequestTimeout(
connectionRequestTimeout).setSocketTimeout(socketTimeout).setConnectTimeout(
connectTimeout).build();
log.info("初始自定義HttpClient......結束");
} catch (Exception e) {
log.error("初始自定義HttpClient......失敗");
}
}
private HttpClientUtil() {
}
private static CloseableHttpClient getHttpClient() {
// 狀態(tài)碼是503的時候,該策略生效
ServiceUnavailableRetryStrategy serviceUnavailableRetryStrategy = new ServiceUnavailableRetryStrategy() {
@Override
public boolean retryRequest(HttpResponse httpResponse, int i, HttpContext httpContext) {
if (i < 3) {
log.info("ServiceUnavailableRetryStrategy========================"+i);
return true;
}
return false;
}
@Override
public long getRetryInterval() {
return 2000L;
}
};
CloseableHttpClient httpClient = HttpClients.custom()
// 設置連接池管理
.setConnectionManager(pool)
// 設置請求配置
.setDefaultRequestConfig(requestConfig)
// 設置重試次數(shù)
.setRetryHandler(new DefaultHttpRequestRetryHandler())
.setServiceUnavailableRetryStrategy(serviceUnavailableRetryStrategy)
.build();
return httpClient;
}
public static String doGet(String url, Map<String, String> param) {
// 創(chuàng)建Httpclient對象
CloseableHttpClient httpClient = getHttpClient();
String resultString = "";
CloseableHttpResponse response = null;
try {
// 創(chuàng)建uri
URIBuilder builder = new URIBuilder(url);
if (param != null) {
for (String key : param.keySet()) {
builder.addParameter(key, param.get(key));
}
}
URI uri = builder.build();
// 創(chuàng)建http GET請求
HttpGet httpGet = new HttpGet(uri);
// 執(zhí)行請求
response = httpClient.execute(httpGet);
// 判斷返回狀態(tài)是否為200
if (response.getStatusLine().getStatusCode() == 200) {
resultString = EntityUtils.toString(response.getEntity(), CHARSET_UTF_8);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (response != null) {
response.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return resultString;
}
public static String doGet(String url) {
return doGet(url, null);
}
public static String doPost(String url, Map<String, String> param) {
// 創(chuàng)建Httpclient對象
CloseableHttpClient httpClient = getHttpClient();
CloseableHttpResponse response = null;
String resultString = "";
try {
// 創(chuàng)建Http Post請求
HttpPost httpPost = new HttpPost(url);
// 創(chuàng)建參數(shù)列表
if (param != null) {
List<NameValuePair> paramList = new ArrayList<>();
for (String key : param.keySet()) {
paramList.add(new BasicNameValuePair(key, param.get(key)));
}
// 模擬表單
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(paramList, CHARSET_UTF_8);
entity.setContentType(CONTENT_TYPE_FORM_URL);
httpPost.setEntity(entity);
}
// 執(zhí)行http請求main
response = httpClient.execute(httpPost);
resultString = EntityUtils.toString(response.getEntity(), CHARSET_UTF_8);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (response != null) {
response.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return resultString;
}
public static String doPost(String url) {
return doPost(url, null);
}
public static String doPostJson(String url, String json) {
// 創(chuàng)建Httpclient對象
CloseableHttpClient httpClient = getHttpClient();
CloseableHttpResponse response = null;
String resultString = "";
try {
// 創(chuàng)建Http Post請求
HttpPost httpPost = new HttpPost(url);
// 創(chuàng)建請求內容
StringEntity entity = new StringEntity(json, ContentType.APPLICATION_JSON);
httpPost.setEntity(entity);
// 執(zhí)行http請求
response = httpClient.execute(httpPost);
resultString = EntityUtils.toString(response.getEntity(), CHARSET_UTF_8);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (response != null) {
response.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return resultString;
}
}
代碼中出現(xiàn)了@Slf4j,作用是引入log,手動打印日志。這個注解是lombok的注解。
解釋一下,什么是Route?
Route的概念可以理解為客戶端機器到目標機器的一條線路,例如使用HttpClient的實現(xiàn)來分別請求 www.163.com 的資源和 www.sina.com 的資源就會產生兩個route。缺省條件下對于每個Route,HttpClient僅維護2個連接,總數(shù)不超過20個連接。
2.3 筆者著重說一下http連接池
1 為什么要使用http連接池?
延遲降低,如果不使用連接池,每次發(fā)起的http請求都會重新建立tcp連接(三次握手),用完就會關閉連接(4次握手),采用連接池則會減少這不是分時間的消耗。連接池管理的對象都是長連接。
支持更大的并發(fā),由于連接池只適用于請求經常訪問同一主機(或同一端口的情況),連接池避免了反復建立連接,搶占端口資源的情況,如果沒用連接池,可能導致連接建立不了。
2 設置超時時間
首先要明白三個概念:socketTimeout,connectTimeout,connectionRequestTimeout。
socketTimeout:客戶端和服務器讀取數(shù)據(jù)的timeoutconnectTimeout:客戶端和服務器建立連接的timeoutconnectionRequestTimeout:從連接池獲取連接的timeout
3 解釋:一次http請求
一次http請求,必定會有三個階段,一:建立連接;二:數(shù)據(jù)傳送;三,斷開連接。
當建立連接在規(guī)定的時間內(ConnectionTimeOut )沒有完成,那么此次連接就結束了。后續(xù)的SocketTimeOutException就一定不會發(fā)生。只有當連接建立起來后,
也就是沒有發(fā)生ConnectionTimeOutException ,才會開始傳輸數(shù)據(jù),如果數(shù)據(jù)在規(guī)定的時間內(SocketTimeOut)傳輸完畢,則斷開連接。否則,觸發(fā)SocketTimeOutException。
三、HttpClient的重試機制
上面說了這么多,就是為了引出下面的重試問題。由于項目中要訪問外部接口,訪問接口的時候,偶爾會出現(xiàn)SocketTimeOutException:Read timed out,其實就是客戶端讀取服務器的數(shù)據(jù)超時了。
3.1. 那么問題來了HttpClient有沒有重試策略?
使用PoolingHttpClientConnectionManager得到的InternalHttpClient實例,是抽象類CloseableHttpClient的一個實現(xiàn)。
看一下ClientExecChain接口的實現(xiàn)類

?
簡單看一下build()方法
public CloseableHttpClient build() {
// 省略一些代碼
// 添加MainClientExec
ClientExecChain execChain = this.createMainExec(requestExecCopy, (HttpClientConnectionManager)connManagerCopy, (ConnectionReuseStrategy)reuseStrategyCopy, (ConnectionKeepAliveStrategy)keepAliveStrategyCopy, new ImmutableHttpProcessor(new HttpRequestInterceptor[]{new RequestTargetHost(), new RequestUserAgent(userAgentCopy)}), (AuthenticationStrategy)targetAuthStrategyCopy, (AuthenticationStrategy)proxyAuthStrategyCopy, (UserTokenHandler)userTokenHandlerCopy);
execChain = this.decorateMainExec(execChain);
// 添加ProtocolExec
ClientExecChain execChain = new ProtocolExec(execChain, httpprocessorCopy);
ClientExecChain execChain = this.decorateProtocolExec(execChain);
// Add request retry executor, if not disabled
if (!automaticRetriesDisabled) {
HttpRequestRetryHandler retryHandlerCopy = this.retryHandler;
if (retryHandlerCopy == null) {
retryHandlerCopy = DefaultHttpRequestRetryHandler.INSTANCE;
}
execChain = new RetryExec(execChain, retryHandlerCopy);
}
// 省去部分代碼
// 如果不為空,添加ServiceUnavailableRetryExec
ServiceUnavailableRetryStrategy serviceUnavailStrategyCopy = this.serviceUnavailStrategy;
if (serviceUnavailStrategyCopy != null) {
execChain = new ServiceUnavailableRetryExec((ClientExecChain)execChain, serviceUnavailStrategyCopy);
}
// 添加RedirectExec
if (!this.redirectHandlingDisabled) {
authSchemeRegistryCopy = this.redirectStrategy;
if (authSchemeRegistryCopy == null) {
authSchemeRegistryCopy = DefaultRedirectStrategy.INSTANCE;
}
execChain = new RedirectExec((ClientExecChain)execChain, (HttpRoutePlanner)routePlannerCopy, (RedirectStrategy)authSchemeRegistryCopy);
}
// 省去部分代碼
return new InternalHttpClient((ClientExecChain)execChain, (HttpClientConnectionManager)connManagerCopy, (HttpRoutePlanner)routePlannerCopy, cookieSpecRegistryCopy, (Lookup)authSchemeRegistryCopy, (CookieStore)defaultCookieStore, (CredentialsProvider)defaultCredentialsProvider, this.defaultRequestConfig != null ? this.defaultRequestConfig : RequestConfig.DEFAULT, closeablesCopy);
}
自上而下,創(chuàng)建了不同的ClientExecChain實例。注意:創(chuàng)建對象的順序就是執(zhí)行器鏈的順序
在構造CloseableHttpClient實例的時候,判斷是否關閉了自動重試功能,automaticRetriesDisabled默認是false。如果沒有指定執(zhí)行器鏈,就用RetryExec。默認的重試策略是DefaultHttpRequestRetryHandler。
如果重寫了ServiceUnavailableRetryStrategy接口,或者使用了DefaultServiceUnavailableRetryStrategy,ServiceUnavailableRetryExec也會加入到執(zhí)行器鏈里。
同理,redirectHandlingDisabled默認是false,RedirectExec也會加入到執(zhí)行器鏈,并且會最先執(zhí)行。
3.2 執(zhí)行流程
前面已經看到我們使用的HttiClient本質上是InternalHttpClient,這里看下他的執(zhí)行發(fā)送數(shù)據(jù)的方法。
@Override
protected CloseableHttpResponse doExecute(
final HttpHost target,
final HttpRequest request,
final HttpContext context) throws IOException, ClientProtocolException {
//省略一些代碼
return this.execChain.execute(route, wrapper, localcontext, execAware);
}
}
首先經過RedirectExec,RedirectExec里面調用ServiceUnavailableRetryExec的excute(),進入ServiceUnavailableRetryExec后,調用RetryExec的excute(),進入發(fā)到RetryExec后,調用ProtocolExec的execute(),最后調用MainClientExec的excute()。
執(zhí)行器鏈結束后,執(zhí)行HttpRequestExecutor的excute(),excute()方法調用了自己的doSendRequest()。
之后一步一步的返回,遇到異常進行處理。
下面是RetryExec發(fā)送請求的部分
public CloseableHttpResponse execute(HttpRoute route,
HttpRequestWrapper request,
HttpClientContext context,
HttpExecutionAware execAware) throws IOException, HttpException {
// 參數(shù)檢驗
Args.notNull(route, "HTTP route");
Args.notNull(request, "HTTP request");
Args.notNull(context, "HTTP context");
// 獲取請求頭的全部信息
Header[] origheaders = request.getAllHeaders();
// 初始化請求次數(shù)為1
int execCount = 1;
while(true) {
try {
// 調用基礎executor執(zhí)行http請求
return this.requestExecutor.execute(route, request, context, execAware);
} catch (IOException var9) {
// 發(fā)生IO異常的時候,判斷上下文是否已經中斷,如果中斷則拋異常退出
if (execAware != null && execAware.isAborted()) {
this.log.debug("Request has been aborted");
throw var9;
}
// 根據(jù)重試策略,判斷當前執(zhí)行狀況是否要重試,如果是則進入下面邏輯
if (!this.retryHandler.retryRequest(var9, execCount, context)) {
if (var9 instanceof NoHttpResponseException) {
NoHttpResponseException updatedex = new NoHttpResponseException(route.getTargetHost().toHostString() + " failed to respond");
updatedex.setStackTrace(var9.getStackTrace());
throw updatedex;
}
throw var9;
}
// 日志
if (this.log.isInfoEnabled()) {
this.log.info("I/O exception (" + var9.getClass().getName() + ") caught when processing request to " + route + ": " + var9.getMessage());
}
// 日志
if (this.log.isDebugEnabled()) {
this.log.debug(var9.getMessage(), var9);
}
// 判斷當前請求是否可以重復發(fā)起
if (!RequestEntityProxy.isRepeatable(request)) {
this.log.debug("Cannot retry non-repeatable request");
throw new NonRepeatableRequestException("Cannot retry request with a non-repeatable request entity", var9);
}
// 設置請求頭
request.setHeaders(origheaders);
// 日志
if (this.log.isInfoEnabled()) {
this.log.info("Retrying request to " + route);
}
++execCount;
}
}
}
當發(fā)生IOException,判斷是否要重試。如果重試則記錄相應的次數(shù),如果不重試,就拋出異常并且退出。
//單例模式 final 不可變的對象,線程安全
public static final DefaultHttpRequestRetryHandler INSTANCE = new DefaultHttpRequestRetryHandler();
//重試次數(shù)
private final int retryCount;
//如果一個請求發(fā)送成功過,是否還會被再次發(fā)送
private final boolean requestSentRetryEnabled;
// 不允許重試的異常類
private final Set<Class<? extends IOException>> nonRetriableClasses;
// 默認重試3次,請求發(fā)送成功,不在發(fā)送
public DefaultHttpRequestRetryHandler() {
this(3, false);
}
public DefaultHttpRequestRetryHandler(final int retryCount, final boolean requestSentRetryEnabled) {
this(retryCount, requestSentRetryEnabled, Arrays.asList(
InterruptedIOException.class,
UnknownHostException.class,
ConnectException.class,
SSLException.class));
}
protected DefaultHttpRequestRetryHandler(
final int retryCount,
final boolean requestSentRetryEnabled,
final Collection<Class<? extends IOException>> clazzes) {
super();
this.retryCount = retryCount;
this.requestSentRetryEnabled = requestSentRetryEnabled;
this.nonRetriableClasses = new HashSet<Class<? extends IOException>>();
for (final Class<? extends IOException> clazz: clazzes) {
this.nonRetriableClasses.add(clazz);
}
}
通過構造函數(shù),可以看出:
重試3次請求成功,就不再重試InterruptedIOException、UnknownHostException、ConnectException、SSLException,發(fā)生這4種異常不重試
- 重試3次
- 請求成功,就不再重試
- InterruptedIOException、UnknownHostException、ConnectException、SSLException,發(fā)生這4種異常不重試
? 關于默認的重試策略
- 如果超過三次不進行重試
- 以上4中異常及其子類不進行重試
- 同一個請求在異步任務中已經停止,不進行重試
- 冪等的方法可以進行重試,比如get,含有http body都可以認為是非冪等
- 請求沒有發(fā)送成功,可以進行重試
問題來了,發(fā)送成功的請求是怎么樣的?
下面的代碼在HttpCoreContext里面,HttpCoreContext是HttpContext的實現(xiàn)類
public static final String HTTP_REQ_SENT = "http.request_sent";
public boolean isRequestSent() {
final Boolean b = getAttribute(HTTP_REQ_SENT, Boolean.class);
return b != null && b.booleanValue();
}
當前httpContext中的http.request_sent設置為true,則認為已經發(fā)送成功。
HttpRequestExecutor的excute(),調用了自己的doSendRequest()。
protected HttpResponse doSendRequest(HttpRequest request,
HttpClientConnection conn,
HttpContext context) throws IOException, HttpException {
// 參數(shù)檢驗
Args.notNull(request, "HTTP request");
Args.notNull(conn, "Client connection");
Args.notNull(context, "HTTP context");
HttpResponse response = null;
// 將連接放入上下文
context.setAttribute("http.connection", conn);
// 在請求發(fā)送之前,將http.request_sent放入上下文context的屬性中,值為false
context.setAttribute("http.request_sent", Boolean.FALSE);
// 將request的header放入連接中
conn.sendRequestHeader(request);
// 如果是post/put這種有body的請求,要先進行判斷
if (request instanceof HttpEntityEnclosingRequest) {
boolean sendentity = true;
// 獲取http協(xié)議版本號
ProtocolVersion ver = request.getRequestLine().getProtocolVersion();
// 滿足100-continue,并且http協(xié)議不是1.0
if (((HttpEntityEnclosingRequest)request).expectContinue() && !ver.lessEquals(HttpVersion.HTTP_1_0)) {
// 刷新當前連接,發(fā)送數(shù)據(jù)
conn.flush();
// Checks if response data is available from the connection
if (conn.isResponseAvailable(this.waitForContinue)) {
// Receives the request line and headers of the next response available from this connection.
response = conn.receiveResponseHeader();
// 判斷相應是否攜帶實體(是否有body)
if (this.canResponseHaveBody(request, response)) {
// Receives the next response entity available from this connection and attaches it to an existing HttpResponse object.
conn.receiveResponseEntity(response);
}
// 獲取請求狀態(tài)碼
int status = response.getStatusLine().getStatusCode();
if (status < 200) {
if (status != 100) {
throw new ProtocolException("Unexpected response: " + response.getStatusLine());
}
response = null;
} else {
sendentity = false;
}
}
}
if (sendentity) {
// 通過連接發(fā)送請求實體
conn.sendRequestEntity((HttpEntityEnclosingRequest)request);
}
}
// Writes out all pending buffered data over the open connection.
conn.flush();
// 將http.request_sent置為true
context.setAttribute("http.request_sent", Boolean.TRUE);
return response;
}
判斷是否攜帶實體的方法
protected boolean canResponseHaveBody(HttpRequest request, HttpResponse response) {
// 如果是head請求,返回false HEAD:只請求頁面首部
if ("HEAD".equalsIgnoreCase(request.getRequestLine().getMethod())) {
return false;
} else {
int status = response.getStatusLine().getStatusCode();
return status >= 200 && status != 204 && status != 304 && status != 205;
}
}
注:HttpEntityEnclosingRequest是一個接口
public interface HttpEntityEnclosingRequest extends HttpRequest {
// 詢問Server是否愿意接收數(shù)據(jù)
boolean expectContinue();
// 設置httpEntity
void setEntity(HttpEntity entity);
// 獲取httpEntity
HttpEntity getEntity();
}
HttpEntityEnclosingRequestBase是實現(xiàn)HttpEntityEnclosingRequest的抽象類
public abstract class HttpEntityEnclosingRequestBase extends HttpRequestBase implements HttpEntityEnclosingRequest {
// HttpEntity其實相當于一個消息實體,內容是http傳送的報文,有多個實現(xiàn)類,常用StringEntity
private HttpEntity entity;
public HttpEntityEnclosingRequestBase() {
}
public HttpEntity getEntity() {
return this.entity;
}
public void setEntity(HttpEntity entity) {
this.entity = entity;
}
// 判斷此請求是否應使用expect-continue
public boolean expectContinue() {
// 從請求頭獲取Except鍵值對
Header expect = this.getFirstHeader("Expect");
// 如果except不為空,并且內容是 100-continue時返回true
return expect != null && "100-continue".equalsIgnoreCase(expect.getValue());
}
public Object clone() throws CloneNotSupportedException {
HttpEntityEnclosingRequestBase clone = (HttpEntityEnclosingRequestBase)super.clone();
if (this.entity != null) {
clone.entity = (HttpEntity)CloneUtils.cloneObject(this.entity);
}
return clone;
}
}
下圖可以看出,HttpPost和HttpPut是HttpEntityEnclosingRequestBase的子類

簡要分析一下,上述的操作過程
- 開始將http.request_sent設置為false
- 通過流flush數(shù)據(jù)到客戶端
- 然后將http.request_sent設置為true
顯然conn.flush()是可以發(fā)生異常的。注意:conn都是從連接池獲取的。
3.3 關閉重試
默認是開啟重試的,可以在創(chuàng)建HttpClientBuilder的時候,調用下面的方法關閉。
public final HttpClientBuilder disableAutomaticRetries() {
this.automaticRetriesDisabled = true;
return this;
}
四、總結
4.1重試發(fā)生的條件
只有發(fā)生IOException才會發(fā)生重試
InterruptedIOException、UnknownHostException、ConnectException、SSLException,發(fā)生這4種異常不重試
get方法可以重試3次,post方法對應的socket流沒有被flush成功時可以重試3次
4.2不發(fā)生重試的異常
InterruptedIOException,線程中斷異常UnknownHostException,找不到對應hostConnectException,找到了host但是建立連接失敗。SSLException,https認證異常
4.3 實踐中遇到的異常
另外,我們還經常會提到兩種超時,連接超時與讀超時:
1. java.net.SocketTimeoutException: Read timed out
2. java.net.SocketTimeoutException: connect timed out
這兩種超時都是SocketTimeoutException,繼承自InterruptedIOException,屬于線程中斷異常,不會進行重試。
以上為個人經驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關文章
Java數(shù)組實現(xiàn)動態(tài)初始化的實例詳解
在本篇文章里小編給大家整理的是一篇關于Java數(shù)組實現(xiàn)動態(tài)初始化的實例詳解內容,有興趣的朋友們可以學習下。2021-10-10
通過Maven進行jedis連接redis的實現(xiàn)
這篇文章主要介紹了通過Maven進行jedis連接redis的實現(xiàn),具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-07-07
Java Web項目中Spring框架處理JSON格式數(shù)據(jù)的方法
Spring MVC是個靈活的框架,返回JSON數(shù)據(jù)的也有很多五花八門的方式,這里我們來整理一個最簡單的Java Web項目中Spring框架處理JSON格式數(shù)據(jù)的方法:2016-05-05

