真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

怎么在springboot中對HttpClient進(jìn)行封裝

怎么在spring boot中對HttpClient進(jìn)行封裝?針對這個(gè)問題,這篇文章詳細(xì)介紹了相對應(yīng)的分析和解答,希望可以幫助更多想解決這個(gè)問題的小伙伴找到更簡單易行的方法。

創(chuàng)新互聯(lián)專業(yè)為企業(yè)提供博愛網(wǎng)站建設(shè)、博愛做網(wǎng)站、博愛網(wǎng)站設(shè)計(jì)、博愛網(wǎng)站制作等企業(yè)網(wǎng)站建設(shè)、網(wǎng)頁設(shè)計(jì)與制作、博愛企業(yè)網(wǎng)站模板建站服務(wù),10年博愛做網(wǎng)站經(jīng)驗(yàn),不只是建網(wǎng)站,更提供有價(jià)值的思路和整體網(wǎng)絡(luò)服務(wù)。

一、Request retry handler(請求重試處理)

為了使自定義異常機(jī)制生效,需要實(shí)現(xiàn)HttpRequestRetryHandler接口,代碼如下:

import java.io.IOException; 
import java.io.InterruptedIOException; 
import java.net.UnknownHostException; 
import javax.net.ssl.SSLException; 
import javax.net.ssl.SSLHandshakeException; 
import org.apache.http.HttpEntityEnclosingRequest; 
import org.apache.http.HttpRequest; 
import org.apache.http.NoHttpResponseException; 
import org.apache.http.client.HttpRequestRetryHandler; 
import org.apache.http.client.protocol.HttpClientContext; 
import org.apache.http.conn.ConnectTimeoutException; 
import org.apache.http.protocol.HttpContext; 
import org.springframework.beans.factory.annotation.Value; 
import org.springframework.context.annotation.Bean; 
import org.springframework.context.annotation.Configuration; 
 
/** 
 * 描述:HttpClient的重試處理機(jī)制 
 */ 
@Configuration 
public class MyhttpRequestRetryHandler { 
 
  @Value("${httpclient.config.retryTime}")// 此處建議采用@ConfigurationProperties(prefix="httpclient.config")方式,方便復(fù)用 
  private int retryTime; 
   
  @Bean 
  public HttpRequestRetryHandler httpRequestRetryHandler() { 
    // 請求重試 
    final int retryTime = this.retryTime; 
    return new HttpRequestRetryHandler() { 
      public boolean retryRequest(IOException exception, int executionCount, HttpContext context) { 
        // Do not retry if over max retry count,如果重試次數(shù)超過了retryTime,則不再重試請求 
        if (executionCount >= retryTime) { 
          return false; 
        } 
        // 服務(wù)端斷掉客戶端的連接異常 
        if (exception instanceof NoHttpResponseException) { 
          return true; 
        } 
        // time out 超時(shí)重試 
        if (exception instanceof InterruptedIOException) { 
          return true; 
        } 
        // Unknown host 
        if (exception instanceof UnknownHostException) { 
          return false; 
        } 
        // Connection refused 
        if (exception instanceof ConnectTimeoutException) { 
          return false; 
        } 
        // SSL handshake exception 
        if (exception instanceof SSLException) { 
          return false; 
        } 
        HttpClientContext clientContext = HttpClientContext.adapt(context); 
        HttpRequest request = clientContext.getRequest(); 
        if (!(request instanceof HttpEntityEnclosingRequest)) { 
          return true; 
        } 
        return false; 
      } 
    }; 
  } 
}

二、Pooling connection manager(連接池管理)

PoolingHttpClientConnectionManager用來管理客戶端的連接池,并且可以為多個(gè)線程的請求提供服務(wù),代碼如下:

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.LayeredConnectionSocketFactory; 
import org.apache.http.conn.socket.PlainConnectionSocketFactory; 
import org.apache.http.conn.ssl.SSLConnectionSocketFactory; 
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; 
import org.springframework.beans.factory.annotation.Value; 
import org.springframework.context.annotation.Bean; 
import org.springframework.context.annotation.Configuration; 
@Configuration 
public class MyPoolingHttpClientConnectionManager { 
  /** 
   * 連接池最大連接數(shù) 
   */ 
  @Value("${httpclient.config.connMaxTotal}") 
  private int connMaxTotal = 20; 
   
  /** 
   * 
   */ 
  @Value("${httpclient.config.maxPerRoute}") 
  private int maxPerRoute = 20; 
 
    /** 
   * 連接存活時(shí)間,單位為s 
   */ 
   @Value("${httpclient.config.timeToLive}") 
   private int timeToLive = 60; 
 
    @Bean 
  public PoolingHttpClientConnectionManager poolingClientConnectionManager(){ 
    PoolingHttpClientConnectionManager poolHttpcConnManager = new PoolingHttpClientConnectionManager(60, TimeUnit.SECONDS); 
    // 最大連接數(shù) 
    poolHttpcConnManager.setMaxTotal(this.connMaxTotal); 
    // 路由基數(shù) 
    poolHttpcConnManager.setDefaultMaxPerRoute(this.maxPerRoute); 
    return poolHttpcConnManager; 
  } 
}

注意:當(dāng)HttpClient實(shí)例不再需要并且即將超出范圍時(shí),重要的是關(guān)閉其連接管理器,以確保管理器保持活動(dòng)的所有連接都被關(guān)閉,并釋放由這些連接分配的系統(tǒng)資源

上面PoolingHttpClientConnectionManager類的構(gòu)造函數(shù)如下:

public PoolingHttpClientConnectionManager(final long timeToLive, final TimeUnit tunit) { 
    this(getDefaultRegistry(), null, null ,null, timeToLive, tunit); 
  } 
 
private static Registry getDefaultRegistry() { 
    return RegistryBuilder.create() 
        .register("http", PlainConnectionSocketFactory.getSocketFactory()) 
        .register("https", SSLConnectionSocketFactory.getSocketFactory()) 
        .build(); 
  }

在PoolingHttpClientConnectionManager的配置中有兩個(gè)最大連接數(shù)量,分別控制著總的最大連接數(shù)量和每個(gè)route的最大連接數(shù)量。如果沒有顯式設(shè)置,默認(rèn)每個(gè)route只允許最多2個(gè)connection,總的connection數(shù)量不超過20。這個(gè)值對于很多并發(fā)度高的應(yīng)用來說是不夠的,必須根據(jù)實(shí)際的情況設(shè)置合適的值,思路和線程池的大小設(shè)置方式是類似的,如果所有的連接請求都是到同一個(gè)url,那可以把MaxPerRoute的值設(shè)置成和MaxTotal一致,這樣就能更高效地復(fù)用連接

特別注意:想要復(fù)用一個(gè)connection就必須要讓它占有的系統(tǒng)資源得到正確釋放,釋放方法如下:

如果是使用outputStream就要保證整個(gè)entity都被write out,如果是inputStream,則再最后要記得調(diào)用inputStream.close()?;蛘呤褂肊ntityUtils.consume(entity)或EntityUtils.consumeQuietly(entity)來讓entity被完全耗盡(后者不拋異常)來做這一工作。EntityUtils中有個(gè)toString方法也很方便的(調(diào)用這個(gè)方法最后也會(huì)自動(dòng)把inputStream close掉的,但是在實(shí)際的測試過程中,會(huì)導(dǎo)致連接沒有釋放的現(xiàn)象),不過只有在可以確定收到的entity不是特別大的情況下才能使用。如果沒有讓整個(gè)entity被fully consumed,則該連接是不能被復(fù)用的,很快就會(huì)因?yàn)樵谶B接池中取不到可用的連接超時(shí)或者阻塞在這里(因?yàn)樵撨B接的狀態(tài)將會(huì)一直是leased的,即正在被使用的狀態(tài))。所以如果想要復(fù)用connection,一定一定要記得把entity fully consume掉,只要檢測到stream的eof,是會(huì)自動(dòng)調(diào)用ConnectionHolder的releaseConnection方法進(jìn)行處理的

三、Connection keep alive strategy(保持連接策略)

HTTP規(guī)范沒有指定持久連接可能和應(yīng)該保持存活多久。一些HTTP服務(wù)器使用非標(biāo)準(zhǔn)的Keep-Alive標(biāo)頭來向客戶端通信它們打算在服務(wù)器端保持連接的時(shí)間段(以秒為單位)。HttpClient可以使用這些信息。如果響應(yīng)中不存在Keep-Alive頭,HttpClient會(huì)假定連接可以無限期地保持活動(dòng)。然而,一般使用的許多HTTP服務(wù)器都配置為在一段不活動(dòng)狀態(tài)之后刪除持久連接,以便節(jié)省系統(tǒng)資源,而不會(huì)通知客戶端。如果默認(rèn)策略過于樂觀,則可能需要提供自定義的保持活動(dòng)策略,代碼如下:

import org.apache.http.HeaderElement; 
import org.apache.http.HeaderElementIterator; 
import org.apache.http.HttpResponse; 
import org.apache.http.conn.ConnectionKeepAliveStrategy; 
import org.apache.http.message.BasicHeaderElementIterator; 
import org.apache.http.protocol.HTTP; 
import org.apache.http.protocol.HttpContext; 
import org.springframework.beans.factory.annotation.Value; 
import org.springframework.context.annotation.Bean; 
import org.springframework.context.annotation.Configuration;  
/** 
 * 描述:連接保持策略 
 * @author chhliu 
 */ 
@Configuration 
public class MyconnectionKeepAliveStrategy { 
   
  @Value("${httpclient.config.keepAliveTime}") 
  private int keepAliveTime = 30; 
   
  @Bean("connectionKeepAliveStrategy") 
  public ConnectionKeepAliveStrategy connectionKeepAliveStrategy() { 
    return new ConnectionKeepAliveStrategy() { 
 
      public long getKeepAliveDuration(HttpResponse response, HttpContext context) { 
        // Honor 'keep-alive' header 
        HeaderElementIterator it = new BasicHeaderElementIterator( 
            response.headerIterator(HTTP.CONN_KEEP_ALIVE)); 
        while (it.hasNext()) { 
          HeaderElement he = it.nextElement(); 
          String param = he.getName(); 
          String value = he.getValue(); 
          if (value != null && param.equalsIgnoreCase("timeout")) { 
            try { 
              return Long.parseLong(value) * 1000; 
            } catch (NumberFormatException ignore) { 
            } 
          } 
        } 
        return 30 * 1000; 
      } 
    }; 
  } 
}

注意:長連接并不使用于所有的情況,尤其現(xiàn)在的系統(tǒng),大都是部署在多臺服務(wù)器上,且具有負(fù)載均衡的功能,如果我們在訪問的時(shí)候,一直保持長連接,一旦那臺服務(wù)器掛了,就會(huì)影響客戶端,同時(shí)也不能充分的利用服務(wù)端的負(fù)載均衡的特性,反而短連接更有利一些,這些需要根據(jù)具體的需求來定,而不是一言概括。

四、HttpClient proxy configuration(代理配置)

用來配置代理,代碼如下:

import org.apache.http.HttpHost; 
import org.apache.http.impl.conn.DefaultProxyRoutePlanner; 
import org.springframework.beans.factory.annotation.Value; 
import org.springframework.context.annotation.Bean; 
import org.springframework.context.annotation.Configuration; 
/** 
 * 描述:HttpClient代理 
 * @author chhliu 
 */ 
@Configuration 
public class MyDefaultProxyRoutePlanner { 
  // 代理的host地址 
  @Value("${httpclient.config.proxyhost}") 
  private String proxyHost; 
   
  // 代理的端口號 
  @Value("${httpclient.config.proxyPort}") 
  private int proxyPort = 8080; 
   
  @Bean 
  public DefaultProxyRoutePlanner defaultProxyRoutePlanner(){ 
    HttpHost proxy = new HttpHost(this.proxyHost, this.proxyPort); 
    return new DefaultProxyRoutePlanner(proxy); 
  } 
}

HttpClient不僅支持簡單的直連、復(fù)雜的路由策略以及代理。HttpRoutePlanner是基于http上下文情況下,客戶端到服務(wù)器的路由計(jì)算策略,一般沒有代理的話,就不用設(shè)置這個(gè)東西。這里有一個(gè)很關(guān)鍵的概念—Route:在HttpClient中,一個(gè)Route指 運(yùn)行環(huán)境機(jī)器->目標(biāo)機(jī)器host的一條線路,也就是如果目標(biāo)url的host是同一個(gè),那么它們的route也是一樣的

五、RequestConfig

用來設(shè)置請求的各種配置,代碼如下:

import org.apache.http.client.config.RequestConfig; 
import org.springframework.beans.factory.annotation.Value; 
import org.springframework.context.annotation.Bean; 
import org.springframework.context.annotation.Configuration;  
@Configuration 
public class MyRequestConfig { 
  @Value("${httpclient.config.connectTimeout}") 
  private int connectTimeout = 2000; 
   
  @Value("${httpclient.config.connectRequestTimeout}") 
  private int connectRequestTimeout = 2000; 
   
  @Value("${httpclient.config.socketTimeout}") 
  private int socketTimeout = 2000; 
  @Bean 
  public RequestConfig config(){ 
    return RequestConfig.custom() 
        .setConnectionRequestTimeout(this.connectRequestTimeout) 
        .setConnectTimeout(this.connectTimeout) 
        .setSocketTimeout(this.socketTimeout) 
        .build(); 
  } 
}

RequestConfig是對request的一些配置。里面比較重要的有三個(gè)超時(shí)時(shí)間,默認(rèn)的情況下這三個(gè)超時(shí)時(shí)間都為0(如果不設(shè)置request的Config,會(huì)在execute的過程中使用HttpClientParamConfig的getRequestConfig中用默認(rèn)參數(shù)進(jìn)行設(shè)置),這也就意味著無限等待,很容易導(dǎo)致所有的請求阻塞在這個(gè)地方無限期等待。這三個(gè)超時(shí)時(shí)間為:

a、connectionRequestTimeout—從連接池中取連接的超時(shí)時(shí)間

這個(gè)時(shí)間定義的是從ConnectionManager管理的連接池中取出連接的超時(shí)時(shí)間, 如果連接池中沒有可用的連接,則request會(huì)被阻塞,最長等待connectionRequestTimeout的時(shí)間,如果還沒有被服務(wù),則拋出ConnectionPoolTimeoutException異常,不繼續(xù)等待。

b、connectTimeout—連接超時(shí)時(shí)間

這個(gè)時(shí)間定義了通過網(wǎng)絡(luò)與服務(wù)器建立連接的超時(shí)時(shí)間,也就是取得了連接池中的某個(gè)連接之后到接通目標(biāo)url的連接等待時(shí)間。發(fā)生超時(shí),會(huì)拋出ConnectionTimeoutException異常。

c、socketTimeout—請求超時(shí)時(shí)間

這個(gè)時(shí)間定義了socket讀數(shù)據(jù)的超時(shí)時(shí)間,也就是連接到服務(wù)器之后到從服務(wù)器獲取響應(yīng)數(shù)據(jù)需要等待的時(shí)間,或者說是連接上一個(gè)url之后到獲取response的返回等待時(shí)間。發(fā)生超時(shí),會(huì)拋出SocketTimeoutException異常。

六、實(shí)例化HttpClient

通過實(shí)現(xiàn)FactoryBean來實(shí)例化HttpClient,代碼如下:

import org.apache.http.client.HttpRequestRetryHandler; 
import org.apache.http.client.config.RequestConfig; 
import org.apache.http.conn.ConnectionKeepAliveStrategy; 
import org.apache.http.impl.client.CloseableHttpClient; 
import org.apache.http.impl.client.HttpClients; 
import org.apache.http.impl.conn.DefaultProxyRoutePlanner; 
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; 
import org.springframework.beans.factory.DisposableBean; 
import org.springframework.beans.factory.FactoryBean; 
import org.springframework.beans.factory.InitializingBean; 
import org.springframework.beans.factory.annotation.Autowired; 
import org.springframework.stereotype.Service;  
/** 
 * 描述:HttpClient客戶端封裝 
 */ 
@Service("httpClientManagerFactoryBen") 
public class HttpClientManagerFactoryBen implements FactoryBean, InitializingBean, DisposableBean { 
 
  /** 
   * FactoryBean生成的目標(biāo)對象 
   */ 
  private CloseableHttpClient client; 
   
  @Autowired 
  private ConnectionKeepAliveStrategy connectionKeepAliveStrategy; 
   
  @Autowired 
  private HttpRequestRetryHandler httpRequestRetryHandler; 
   
  @Autowired 
  private DefaultProxyRoutePlanner proxyRoutePlanner; 
   
  @Autowired 
  private PoolingHttpClientConnectionManager poolHttpcConnManager; 
   
  @Autowired 
  private RequestConfig config; 
   
    // 銷毀上下文時(shí),銷毀HttpClient實(shí)例 
  @Override 
  public void destroy() throws Exception { 
         /* 
      * 調(diào)用httpClient.close()會(huì)先shut down connection manager,然后再釋放該HttpClient所占用的所有資源, 
      * 關(guān)閉所有在使用或者空閑的connection包括底層socket。由于這里把它所使用的connection manager關(guān)閉了, 
      * 所以在下次還要進(jìn)行http請求的時(shí)候,要重新new一個(gè)connection manager來build一個(gè)HttpClient, 
      * 也就是在需要關(guān)閉和新建Client的情況下,connection manager不能是單例的. 
      */ 
        if(null != this.client){ 
      this.client.close(); 
      } 
  } 
 
  @Override// 初始化實(shí)例 
  public void afterPropertiesSet() throws Exception { 
         /* 
     * 建議此處使用HttpClients.custom的方式來創(chuàng)建HttpClientBuilder,而不要使用HttpClientBuilder.create()方法來創(chuàng)建HttpClientBuilder 
     * 從官方文檔可以得出,HttpClientBuilder是非線程安全的,但是HttpClients確實(shí)Immutable的,immutable 對象不僅能夠保證對象的狀態(tài)不被改變, 
     * 而且還可以不使用鎖機(jī)制就能被其他線程共享 
     */ 
         this.client = HttpClients.custom().setConnectionManager(poolHttpcConnManager) 
        .setRetryHandler(httpRequestRetryHandler) 
        .setKeepAliveStrategy(connectionKeepAliveStrategy) 
        .setRoutePlanner(proxyRoutePlanner) 
        .setDefaultRequestConfig(config) 
        .build(); 
  } 
 
    // 返回實(shí)例的類型 
  @Override 
  public CloseableHttpClient getObject() throws Exception { 
    return this.client; 
  } 
 
  @Override 
  public Class getObjectType() { 
    return (this.client == null ? CloseableHttpClient.class : this.client.getClass()); 
  } 
 
    // 構(gòu)建的實(shí)例為單例 
  @Override 
  public boolean isSingleton() { 
    return true; 
  } 
 
}

七、增加配置文件

# 代理的host 
httpclient.config.proxyhost=xxx.xx.xx.xx 
# 代理端口 
httpclient.config.proxyPort=8080 
# 連接超時(shí)或異常重試次數(shù) 
httpclient.config.retryTime=3 
# 長連接保持時(shí)間,單位為s 
httpclient.config.keepAliveTime=30 
# 連接池最大連接數(shù) 
httpclient.config.connMaxTotal=20 
httpclient.config.maxPerRoute=20 
# 連接超時(shí)時(shí)間,單位ms 
httpclient.config.connectTimeout=2000 
# 請求超時(shí)時(shí)間 
httpclient.config.connectRequestTimeout=2000 
# sock超時(shí)時(shí)間 
httpclient.config.socketTimeout=2000 
# 連接存活時(shí)間,單位s 
httpclient.config.timeToLive=60

八、測試

測試代碼如下:

import java.io.IOException; 
import java.util.concurrent.ExecutorService; 
import java.util.concurrent.Executors; 
 
import javax.annotation.Resource; 
 
import org.apache.http.Consts; 
import org.apache.http.ParseException; 
import org.apache.http.client.ClientProtocolException; 
import org.apache.http.client.methods.CloseableHttpResponse; 
import org.apache.http.client.methods.HttpGet; 
import org.apache.http.impl.client.CloseableHttpClient; 
import org.apache.http.util.EntityUtils; 
import org.junit.Test; 
import org.junit.runner.RunWith; 
import org.springframework.boot.test.context.SpringBootTest; 
import org.springframework.test.context.junit4.SpringRunner; 
 
@RunWith(SpringRunner.class) 
@SpringBootTest 
public class HttpClientManagerFactoryBenTest { 
    // 注入HttpClient實(shí)例 
    @Resource(name = "httpClientManagerFactoryBen") 
  private CloseableHttpClient client; 
   
  @Test 
  public void test() throws ClientProtocolException, IOException, InterruptedException{ 
    ExecutorService service = Executors.newFixedThreadPool(2); 
    for(int i=0; i<10; i++){ 
      service.submit(new Runnable() { 
         
        @Override 
        public void run() { 
          System.out.println("the current thread is:"+Thread.currentThread().getName()); 
                    HttpEntity entity = null; 
                    try { 
            HttpGet get = new HttpGet("https://localhost:8080/testjson"); 
            // 通過httpclient的execute提交 請求 ,并用CloseableHttpResponse接受返回信息 
            CloseableHttpResponse response = client.execute(get); 
            System.out.println("client object:"+client); 
                        entity = response.getEntity(); 
                        System.out.println("============"+EntityUtils.toString(entity, Consts.UTF_8)+"============="); 
                        EntityUtils.consumeQuietly(entity);// 釋放連接 
                    } catch (ClientProtocolException e) { 
            e.printStackTrace(); 
          } catch (ParseException e) { 
            e.printStackTrace(); 
          } catch (IOException e) { 
            e.printStackTrace(); 
          } finally{ 
                        if(null != entity){// 釋放連接 
                EntityUtils.consumeQuietly(entity); 
               } 
                    } 
                } 
      }); 
    } 
    Thread.sleep(60000); 
  } 
}

關(guān)于怎么在spring boot中對HttpClient進(jìn)行封裝問題的解答就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道了解更多相關(guān)知識。


網(wǎng)頁名稱:怎么在springboot中對HttpClient進(jìn)行封裝
網(wǎng)頁鏈接:http://weahome.cn/article/gcggoi.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部