RestTemplate全網最強總結(永久更新)

gooogle發表於2021-01-14

@TOC

目的是在需要的時候以最快的、無bug、可承載大併發大資料下的標準使用。
可供日常學習、編寫指令碼、開發時使用。
希望閱讀本文後,不會再被任何http、介面請求類的問題嚇到!有問題找blog,解決不了請留言,會及時補充,永久更新!。

整體架構圖

在這裡插入圖片描述

基本使用與原理

RestTemplate主要方法

http方法 RestTemplate方法
GET getForObject(String url, Class responseType,Object… urlVariables)
POST postForObject(String url, Object request,Class responseType,Object… urlVariables)
PUT put(String url, Object request,Object… urlVariables)
DELETE delete(String url,Object… urlVariables)
HEAD headForHeaders(String url,Map<String,?> urlVariables)
OPTIONS optionsForAllow(String url,Object… urlVariables)
推薦這些方法我們都不記,只記錄一個:exchange

URI與引數

URL

  • 建立URL使用Uri Builder類,常見的方法有:
    public static UriComponentsBuilder newInstance();
    public static UriComponentsBuilder fromUri(URI uri);
    public static UriComponentsBuilder fromHttpRequest(HttpRequest request);
    public static UriComponentsBuilder fromUriString(String uri);
    其中最常用的為fromUriString,本文以此方法為主。
    原理如截圖,透過給定的url,使用正規表示式,分塊匹配url字串中的每一部分,得到scheme,host,port…等資訊,最後build到物件屬性中。
    fromUriString方法
    示例:
// 使用newInstance方法構建,不推薦
UriComponents uriComponents = UriComponentsBuilder.newInstance()
        .scheme("http")
        .host("www.test2.com")
        .path("/test")
        .queryParam("query", "a")
        .build();
// 使用fromUriString,強烈推薦
String url = UriComponentsBuilder.fromUriString("http://www.baidu.com/test?query1=1").build();
  • UriComponentsBuilder常用方法:
方法 介紹
queryParam 新增get引數,如果存在則替換,否則新增。例子:queryParam(“query2”, 2)
replaceQueryParam 替換get引數,與queryParam類似,為了增強語意
queryParams 批次增加get引數,入參是值 一個Key對應多個Value的MultiValueMap
replaceQueryParams 先清空引數列表,再批次增加get引數,入參是MultiValueMap
expand 替換所有url佔位符
encode 對url字串進行編碼,預設是utf-8,編碼格式可以在引數中指定
toUriString 輸出url最終字串,會自動呼叫encode進行編碼
toString 輸出url原始字串,不會自動呼叫encode進行編碼
對於編碼此處特殊程式碼說明:
/**
* 情況一
*/
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString("http://www.test2.com/test?query=哈哈");
// 可以編碼成功
String s = builder.toUriString();

/**
* 情況二
*/
UriComponents uriComponents = UriComponentsBuilder.newInstance()
        .scheme("http")
        .host("www.test2.com")
        .path("/test")
        .queryParam("query", "a")
        .build();
// 編碼失敗
String s = uriComponents.toUriString();
// 正確的方式,需要使用builder類的toUriString(),使用UriComponents類的toUriString不起作用,可以用encode手動實現
String s = uriComponents.encode().toString();

// 總結,沒事別用newInstance構造,老老實實使用fromUriString
  • UriComponents類
    可以理解為是一個Uri的構造體,可以透過此類拿到想要的uri部件中的各種資訊,如host、queryparam等。可以透過UriComponentsBuilder.build方法獲取UriComponents。並透過UriComponents.encode.toString()方法對url進行編碼,效果為urlenconde。常用的為簡化版:
    UriComponents.toUriString();
    常用方法:
public void test01(){
        UriComponentsBuilder u = UriComponentsBuilder.fromUriString("http://www.test.com/{grd}/test?query1=1");
        UriComponents uriComponents = u.build().expand("heihei");
        String s = uriComponents.toUriString();
        String query = uriComponents.getQuery();
        MultiValueMap<String, String> queryParams = uriComponents.getQueryParams();
        String getPath = uriComponents.getPath();
        List<String> getPathSegments = uriComponents.getPathSegments();
        String getUserInfo = uriComponents.getUserInfo();
        String getFragment = uriComponents.getFragment();
        System.out.println(s);
        System.out.println("query:"+query);
        System.out.println("queryParams"+queryParams);
        System.out.println("getPath:"+getPath);
        System.out.println("getPathSegments:"+getPathSegments);
    }
// 輸出結果
http://www.test.com/heihei/test?query1=1
query:query1=1
queryParams{query1=[1]}
getPath:/heihei/test
getPathSegments:[heihei, test]

引數

每一個方法,第一個引數都是uri,uri除了手寫,都可以透過URL類去構建。
引數可以用陣列傳遞進去(陣列指的是動態引數),也可以用Map代替。

// 動態陣列
String result = restTemplate.getForObject("http://aaa.com/test/{param1}/tt/{param2}", String.class, "11", "22");
// Map
Map<String, String> vars = new HashMap<String, String>();
vars.put("param1", "11");
vars.put("param2", "22");
String result = restTemplate.getForObject("http://aaa.com/test/{param1}/tt/{param2", String.class, vars);

HttpEntity與RequestEntity

HttpEntity

HttpEntity對header與body進行了封裝,它的結構體非常簡單:

public class HttpEntity<T> {
    private final HttpHeaders headers;    
    private final T body;
}

其中HttpHeaders是一個MultiValueMap<String, String>,總結如下:

  • 幾乎所有的header都封裝了常量,所以以後設定header不用再傻傻的拼寫了,直接在此類搜尋。如HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS等
  • 注意字符集編碼不要傻傻的寫UTF_8了,用常量StandardCharsets
  • 此類還提供了一個BasicAuth的小實現:HttpHeaders.encodeBasicAuth(“gurundong”, “123456”, StandardCharsets.UTF_8);
  • HttpEntity.EMPTY,建立一個空的HttpEntity,主要用於在exchange方法中使用,簡寫。
  • 示例程式碼:
public void test01(){
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.add(HttpHeaders.ACCEPT,"13");
        httpHeaders.add(HttpHeaders.ACCEPT_CHARSET,"24");
        System.out.println(httpHeaders.toString());
 }

RequestEntity

RequestEntity extends HttpEntity,又新封裝了HttpMethod與URI屬性。實際配合exchange使用時,個人感覺直接用HttpEntity更好,程式碼更直觀。

ClientHttpRequestFactory

RestTemplate整合了HttpAccessor基礎抽象類,在HttpAccessor中,定義了
requestFactory預設實現。也可以透過RestTemplate(ClientHttpRequestFactory requestFactory)的建構函式,傳入自定義的ClientHttpRestFactory。

HttpUrlConnection實現

  • SimpleClientHttpRequestFactory的實現,底層為JDK自帶的HttpURLConnection,每執行一次exchange方法,都會新開一個tcp連結控制程式碼。 請求 < = > tcp連結一一對應。linux預設的tcp連結控制程式碼限制是1024(雖然可以透過配置調大),這會極大的限制併發,不能夠複用tcp通道,浪費大量的tcp連結。
    private ClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();

HttpClinet實現

除了JDK自帶HttpURLConnection實現的SimpleClientHttpRequestFactory,其它常用的還有HttpComponentsClientHttpRequestFactory、OkHttp3ClientHttpRequestFactory等。我們只看由HttpClient實現的HttpComponentsClientHttpRequestFactory。

  • HttpComponentsClientHttpRequestFactory有幾種構造方法,其中最常用的有無參構造方法、以及有參(HttpClient httpClient)構造方法。在生產環境,一定不可使用無參構方法,因為不配置會使用配置配置,由httpClient構建的預設連線池會非常小(預設最大連線是20,每個路由最大連線是2)。
  • 此處簡單小補一下HttpClient的連線池原理圖:
    HttpClient連線池原理圖
    連線池配置
  • 附上http自定義HttpClient連線池的程式碼,以後方便直接使用
@Configuration
public class RestTemplateConfig {
    Logger logger = LoggerFactory.getLogger(RestTemplateConfig.class);

    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate(clientHttpRequestFactory());
    }

    @Bean
    public ClientHttpRequestFactory clientHttpRequestFactory() {
        /**
         * 連結配置
         */
        RequestConfig.Builder configBuilder = RequestConfig.custom();
        // 設定連線超時,連結到目標介面 30秒
        configBuilder.setConnectTimeout(30000);
        // 設定讀取超時,等待目標介面超時 60秒
        configBuilder.setSocketTimeout(60000);
        // 設定從連線池獲取連線例項的超時,等待連線池可用連結超時 10秒
        configBuilder.setConnectionRequestTimeout(10000);
        // 在提交請求之前 測試連線是否可用
//        configBuilder.setStaleConnectionCheckEnabled(true);
        //cookie管理規範設定,此處有多種可以設定,按需要設定
//        configBuilder.setCookieSpec(CookieSpecs.BROWSER_COMPATIBILITY);
        RequestConfig requestConfig = configBuilder.build();

        /**
         * 池化定義
         */
        // 預設該實現會為每個路由保持2個並行連線,總的數量上不超過20個連線
        PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
        // 連線池的最大連線數
        cm.setMaxTotal(200);
        // 每個路由的最大連線數
        cm.setDefaultMaxPerRoute(20);
        // 某個路由的最大連線數
        HttpHost localhost = new HttpHost("www.baidu.com", 80);
        cm.setMaxPerRoute(new HttpRoute(localhost), 50);

        /**
         * 模擬瀏覽器cookie,設定到全域性httpClient上,每次都傳送都可以攜帶
         */
        CookieStore cookieStore = new BasicCookieStore();
        BasicClientCookie cookie = new BasicClientCookie("name", "value");
        cookie.setDomain("www.grd.com");
        cookie.setPath("/");
        cookieStore.addCookie(cookie);

        /**
         * 配置定義
         */
        // 建立可服用的httpClient
//        CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(cm).build();
        // httpClientBuilder配置構架器
        HttpClientBuilder httpClientBuilder = HttpClients.custom();
        // 設定預設請求配置
        httpClientBuilder.setDefaultRequestConfig(requestConfig);
        // 設定重試次數,此處注意,如果使用無參構造,重試次數為3。this(3, false);
        httpClientBuilder.setRetryHandler(new DefaultHttpRequestRetryHandler(2, false));
        // 設定預設cookie配置
//        httpClientBuilder.setDefaultCookieStore(cookieStore);
        // 設定預設https配置
//        httpClientBuilder.setSSLSocketFactory(createSSLConn());
//        httpClientBuilder.setDefaultConnectionConfig()
        // 設定預設header配置
//        httpClientBuilder.setDefaultHeaders();


        // 獲取httpClient
        CloseableHttpClient httpClient = httpClientBuilder.build();

        // 配置HttpClient的對應工廠HttpComponentsClientHttpRequestFactory
        HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient);
        factory.setConnectTimeout(10000);   // 連結超時
        factory.setReadTimeout(10000);
        factory.setConnectionRequestTimeout(1000);
        return factory;
    }

    @Bean
    private static SSLConnectionSocketFactory createSSLConn() {
        SSLConnectionSocketFactory sslsf = null;
        try
        {
            SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
                public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                    return true;
                }
            }).build();
            sslsf = new SSLConnectionSocketFactory(sslContext);
        } catch (GeneralSecurityException e)
        {
            e.printStackTrace();
        }
        return sslsf;
    }


    /**
     * 自定義的重試策略,需要的時候使用
     */
    public void customHttpRequestRetryHandler(){
        //請求失敗時,進行請求重試
        HttpRequestRetryHandler handler = new HttpRequestRetryHandler() {
            @Override
            public boolean retryRequest(IOException e, int i, HttpContext httpContext) {
                if (i > 3){
                    //重試超過3次,放棄請求
                    logger.error("retry has more than 3 time, give up request");
                    return false;
                }
                if (e instanceof NoHttpResponseException){
                    //伺服器沒有響應,可能是伺服器斷開了連線,應該重試
                    logger.error("receive no response from server, retry");
                    return true;
                }
                if (e instanceof SSLHandshakeException){
                    // SSL握手異常
                    logger.error("SSL hand shake exception");
                    return false;
                }
                if (e instanceof InterruptedIOException){
                    //超時
                    logger.error("InterruptedIOException");
                    return false;
                }
                if (e instanceof UnknownHostException){
                    // 伺服器不可達
                    logger.error("server host unknown");
                    return false;
                }
                if (e instanceof ConnectTimeoutException){
                    // 連線超時
                    logger.error("Connection Time out");
                    return false;
                }
                if (e instanceof SSLException){
                    logger.error("SSLException");
                    return false;
                }

                HttpClientContext context = HttpClientContext.adapt(httpContext);
                HttpRequest request = context.getRequest();
                if (!(request instanceof HttpEntityEnclosingRequest)){
                    //如果請求不是關閉連線的請求
                    return true;
                }
                return false;
            }
        };
    }
}
如何使用HttpClient的全套功能

很簡單,可以直接透過HttpComponentsClientHttpRequestFactory拿到HttpClient!直接繞過RestTemplate,享用原生HttpClient的所有功能。

HttpComponentsClientHttpRequestFactory requestFactory = (HttpComponentsClientHttpRequestFactory) restTemplate.getRequestFactory();
HttpClient httpClient = requestFactory.getHttpClient();

ResponseEntity

  • ResponseEntity繼承與HttpEntity,新增了status成員變數。使用ResponseEntity作為controller的返回值,我們可以方便地處理響應的header,狀態碼以及body。而通常使用的@ResponseBody註解,只能處理body部分。這也是為什麼通常在下載場景中會使用ResponseEntity,因為下載需要設定header裡的content-type以及特殊的status(比如206)。
  • ResponseBody可以直接返回Json結果,ResponseEntity不僅可以返回json結果,還可以定義返回的HttpHeaders和HttpStatus。
  • 溫習一下ResponseEntity在Spring mvc中的實現:HttpEntityMethodProcessor的handleReturnValue方法
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
            ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
        // 獲取ServletReuqest 與 ServletResponse
        ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
        ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);

        Assert.isInstanceOf(HttpEntity.class, returnValue);
        // 獲取到responseEntity
        HttpEntity<?> responseEntity = (HttpEntity<?>) returnValue;

        // 將responseEntity的Header資訊加入到ServletResponse
        HttpHeaders outputHeaders = outputMessage.getHeaders();
        HttpHeaders entityHeaders = responseEntity.getHeaders();
        if (!entityHeaders.isEmpty()) {
            entityHeaders.forEach((key, value) -> {
                if (HttpHeaders.VARY.equals(key) && outputHeaders.containsKey(HttpHeaders.VARY)) {
                    List<String> values = getVaryRequestHeadersToAdd(outputHeaders, entityHeaders);
                    if (!values.isEmpty()) {
                        outputHeaders.setVary(values);
                    }
                }
                else {
                    outputHeaders.put(key, value);
                }
            });
        }

        // 如果是ResponseEntity,直接flush,返回資料。
        if (responseEntity instanceof ResponseEntity) {
            int returnStatus = ((ResponseEntity<?>) responseEntity).getStatusCodeValue();
            outputMessage.getServletResponse().setStatus(returnStatus);
            if (returnStatus == 200) {
                if (SAFE_METHODS.contains(inputMessage.getMethod())
                        && isResourceNotModified(inputMessage, outputMessage)) {
                    // Ensure headers are flushed, no body should be written.
                    outputMessage.flush();
                    // Skip call to converters, as they may update the body.
                    return;
                }
            }
            else if (returnStatus / 100 == 3) {
                String location = outputHeaders.getFirst("location");
                if (location != null) {
                    saveFlashAttributes(mavContainer, webRequest, location);
                }
            }
        }

        // 轉換body
        writeWithMessageConverters(responseEntity.getBody(), returnType, inputMessage, outputMessage);

        // Ensure headers are flushed even if no body was written.
        outputMessage.flush();
  • ResponseEntity在RestTemplate中的應用,非常簡單,ResponseEntity只是用來做返回值的載體,封裝了header、httpStatus、body資訊,方便熟悉Spring mvc的人使用。原理程式碼如下:
private class ResponseEntityResponseExtractor<T> implements ResponseExtractor<ResponseEntity<T>> {
        @Override
        public ResponseEntity<T> extractData(ClientHttpResponse response) throws IOException {
            // 此步驟將ClientHttpResponse中的InputStream型別的body,轉化為T型別的Body。
            T body = this.delegate.extractData(response);
            // 將statusCode、HttpHeader、Body封裝入ResponseEntity以供使用。
            return ResponseEntity.status(response.getRawStatusCode()).headers(response.getHeaders()).body(body);

        }
    }

ParameterizedTypeReference

RestTemplate異常整理

在請求傳送時,返回不是200都會拋異常。
注意HttpStatusCodeException、HttpClientErrorException、HttpServerErrorException,主要使用這三個。
異常圖示

RestTemplate最關鍵的exchange方法(不想學原理只想使用的直接看這裡)

使用demo

  • http
public class Test {
    public static void main(String[] args) {
//        RestTemplate template = new RestTemplate();
//        ResponseEntity<String> exchange = template.exchange("https://blog.csdn.net/qq_37099837/article/details/112461675", HttpMethod.GET, HttpEntity.EMPTY, String.class);
//        System.out.println(exchange.getStatusCodeValue());

        RestTemplate restTemplate = new RestTemplate();
        // 加列印url與header的interceptor
        restTemplate.setInterceptors(Collections.singletonList(printTemplateInterceptor()));
        // 設定header
        MultiValueMap<String,String> header = new LinkedMultiValueMap<>();
        // 設定cookie
        String cookies = "xxwww:xxxsss;";
        header.add(HttpHeaders.COOKIE,cookies);
        HttpEntity<MultiValueMap<String,String>> httpEntity = new HttpEntity<>(header);
        // 設定路徑引數
        MultiValueMap<String,String> vars = new LinkedMultiValueMap<>();
        vars.add("param1","grd");
        // url
        UriComponentsBuilder builder = UriComponentsBuilder.fromUriString("http://www.test2.com/{param1}/test");
        // 新增get引數
        builder.queryParam("query","dfadsf");
        ResponseEntity<String> responseEntity = restTemplate.exchange(builder.toUriString(), HttpMethod.GET, httpEntity, String.class,vars);
        String res = responseEntity.getBody();
        System.out.println(res);
    }

    public static ClientHttpRequestInterceptor printTemplateInterceptor(){
        return new ClientHttpRequestInterceptor(){
            public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
                // 透過請求獲取請求中的header引數
                HttpHeaders headers = request.getHeaders();
                System.out.println("url:"+request.getURI().toString());
                System.out.println("headers:"+headers);
                return execution.execute(request, body);
            }
        };
    }
}
  • https
    此處只講針對HttpClient實現的配置:
HttpClientBuilder httpClientBuilder = HttpClients.custom();
httpClientBuilder.setSSLSocketFactory(createSSLConn());
// 獲取httpClient
CloseableHttpClient build = httpClientBuilder.build();
// 配置HttpClient的對應工廠HttpComponentsClientHttpRequestFactory
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(build);
new RestTemplate(factory)

// Https配置
private static SSLConnectionSocketFactory createSSLConn() {
   SSLConnectionSocketFactory sslsf = null;
   try
   {
       SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
           public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
               return true;
           }
       }).build();
       sslsf = new SSLConnectionSocketFactory(sslContext);
   } catch (GeneralSecurityException e)
   {
       e.printStackTrace();
   }
   return sslsf;
}
  • 連線池配置:
    直接看ClientHttpRequestFactory 章節

    原理

    exchange方法只是做了簡單的封裝,最終呼叫doExecute。
    一般就用這個方法,提供url、HttpMethod、HttpEntity、responseType即可
    public <T> ResponseEntity<T> exchange(String url, HttpMethod method,HttpEntity<?> requestEntity, Class<T> responseType, Object... uriVariables) throws RestClientException {
          // 將HttpEntity、responseType封裝到統一的HttpEntityRequestCallback中
          RequestCallback requestCallback = httpEntityCallback(requestEntity, responseType);
          // 將responseType封裝到ResponseEntityResponseExtractor中
          ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType);
          return nonNull(execute(url, method, requestCallback, responseExtractor, uriVariables));
      }
    最關鍵的doExecute解析。主要分為4個步驟:
    建立ClientHttpRequest物件->Java Class轉化為請求body輸出流->執行請求->錯誤處理->響應body轉化為Java Class
    protected <T> T doExecute(URI url, @Nullable HttpMethod method, @Nullable RequestCallback requestCallback,
              @Nullable ResponseExtractor<T> responseExtractor) throws RestClientException {
          ...省略
          try {
              // 建立ClientHttpRequest物件方法,實際會呼叫getRequestFactory().createRequest(url, method)
              ClientHttpRequest request = createRequest(url, method);  
              if (requestCallback != null) {
              // 進行實際請求前對ClientHttpRequest物件的繼續賦值補充,裡面最關鍵的一步是透過遍歷可用的HttpMessageConverter,透過請求body體的Class型別+http的content-type requestBodyClass來進行java類到body OutputStream的轉換
                  requestCallback.doWithRequest(request);
              }
              // 實際執行請求,返回ClientHttpResponse
              response = request.execute();
              // 通用錯誤處理,預設使用DefaultResponseErrorHandler
              handleResponse(url, method, response);
              // 響應中body體到java Class的轉換,最後返回java Class,流程與上面的doWithRequest類似
              return (responseExtractor != null ? responseExtractor.extractData(response) : null);
          }
          catch (IOException ex) {
              ...
          }
      }
    分步解析:Java Class 與 body 的互轉通用程式碼
// 遍歷可用的HttpMessageConverter
...
// 透過Java Class與content-type判斷是否執行此HttpMessageConverter轉換
if(messageConverter.canWrite(requestBodyClass, requestContentType)) {
        //執行轉換,將轉換後的結果放入httpRequest的body中
        ((HttpMessageConverter<Object>) messageConverter).write(requestBody, requestContentType,httpRequest);
}
其中,HttpMessageConverterRestTemplate初始化的時候就已經載入了,舉例載入jackson的原理:

```java
private static final boolean jackson2Present =
            ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper",
                    RestTemplate.class.getClassLoader()) &&
            ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator",
                    RestTemplate.class.getClassLoader());
public RestTemplate() {
        ...
        if (jackson2XmlPresent) {
            this.messageConverters.add(new MappingJackson2XmlHttpMessageConverter());
        }
        ...
    }
錯誤處理程式碼:

```java
ResponseErrorHandler errorHandler = getErrorHandler();
        // 判斷是否有錯
        boolean hasError = errorHandler.hasError(response);
        // 有錯呼叫handleError,預設DefaultResponseErrorHandler實現是拋異常。
        if (hasError) {
            errorHandler.handleError(url, method, response);
        }

RestTemplate責任鏈模式ClientHttpRequestInterceptor

原理很簡單,RestTemplate繼承了InterceptingHttpAccessor,RestTemplate extends InterceptingHttpAccessor implements HttpAccessor,其中getRequestFactory()方法被重寫了:

// 如果interceptors不為空,則使用InterceptingClientHttpRequestFactory工廠。
if (!CollectionUtils.isEmpty(interceptors)) {
            ClientHttpRequestFactory factory = this.interceptingRequestFactory;
            if (factory == null) {
                factory = new InterceptingClientHttpRequestFactory(super.getRequestFactory(), interceptors);
                this.interceptingRequestFactory = factory;
            }
            return factory;
}

InterceptingClientHttpRequestFactory工廠使用InterceptingRequestExecution執行execute方法,就是重寫了上面我們提到的透過ClientHttpRequest.execute()獲取ClientHttpResponse的過程。
在InterceptingClientHttpRequestFactory的execute方法中,先執行完所有的interceptors,其中,interceptors的結構是List,不管使用arrayList或者LinkedList,都是有序的,按順序執行完所有的攔截器,最後執行實際請求程式碼。
上簡單易懂的原始碼:

@Override
public ClientHttpResponse execute(HttpRequest request, byte[] body) throws IOException {
    // 如果存在攔截器,則先執行完攔截器,每一次執行都可以更改Header、uri、以及body等資訊。
    if (this.iterator.hasNext()) {
        ClientHttpRequestInterceptor nextInterceptor = this.iterator.next();
        // 此處比較有意思,執行的是類似與dfs的遞迴呼叫
        return nextInterceptor.intercept(request, body, this);
    }
    // 當dfs條件不滿足,執行完攔截器後,進行實際的傳送請求,得到最終的ClientHttpResponse
    else {
        .......
        return delegate.execute();
    }
}

如果此攔截器不是最後一個,想繼續呼叫其它攔截器,則最後使用return execution.execute(request, body);繼續遞迴呼叫其下一個攔截器。如果不想再遞迴了,作為最後一個攔截器,可以使用自行實現的方法execute(如ribben),直接return ClientHttpResponse即可。

為方便理解,再來一段簡單的程式碼,自動加header實現微服務間的呼叫:

public ClientHttpRequestInterceptor restAuthorizationTemplateInterceptor(){
       return new ClientHttpRequestInterceptor(){
             @Override
             public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
                     // 透過請求獲取請求中的header引數
                     HttpHeaders headers = request.getHeaders();
                     // 往header中設定自己需要的引數
                     headers.add("Authorization", getAuthorization());
                      headers.add("Content-Type", "application/json");
                      headers.add("Accept", MediaType.APPLICATION_JSON.toString());
                      return execution.execute(request, body);
               }
        };
 }

@Bean
public RestTemplate restTemplate() {
    RestTemplate restTemplate = new RestTemplate();
    List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>();
    // 攔截器按照新增的順序執行
    interceptors.add(restAuthorizationTemplateInterceptor());
    restTemplate.setInterceptors(interceptors);
    return restTemplate;
}

RestTemple與Springcloud

@LoadBalanced實現請求負載均衡

透過攔截器在RestTemplate工作前進行負載均衡。該攔截器中注入了LoadBalancerClient(負載均衡客戶端)的實現。

// 使用了一個LoadBalancerInterceptor攔截器,實現對uri的重寫
public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {
    //Ribbon客戶端
    private LoadBalancerClient loadBalancer;

    ...

    public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) throws IOException {
    ....
    //使用Ribbon執行呼叫
    return (ClientHttpResponse)this.loadBalancer.execute(serviceName, this.requestFactory.createRequest(request, body, execution));
    }
}

透過上面的程式碼我們看出請求最終交給LoadBalanceClient例項去執行,LoadBalanceClient是Ribbon提供的負載均衡客戶端,很多重要的邏輯都在這裡處理。如下為LoadBalanceClient的類圖:

RestTemple與其它框架(如HttpClient)是如何整合的?

詳細見本文的ClientHttpRequestFactory章節

如何進行併發請求?

內容過多,原理單獨篇章實現吧。
僅做以下幾點總結,不展示程式碼了,大家都會:

  • 多執行緒實現的併發,在主執行緒維度看,屬於非同步阻塞,在工作執行緒維度看,屬於同步阻塞。併發量一般,做一般的業務夠了。
  • 在底層看,實現非阻塞必須使用epoll,或者select、pool實現。
    非同步可以使用開執行緒、協程實現。
    頂級模型為協程模型,自動實現epoll事件排程(非阻塞)+開協程(非同步)。
    java實現為基於netty的Reactors 多執行緒模型,使用執行緒實現非同步,epool實現非阻塞。效率可與協程媲美。
  • 同步阻塞:普通呼叫屬於同步阻塞,開多執行緒處理在工作執行緒維度看依舊屬於同步阻塞。
    同步非阻塞、
    非同步阻塞:開多執行緒處理,在主執行緒維度看,屬於非同步阻塞。
    非同步非阻塞、
  • 超大併發,想借力netty,可使用WebClient

當請求書併發過高,產生的連線池不夠問題。

檢視ClientHttpRequestFactory的連線池配置章節,對連線數不夠產生的原因與解決方案做了解釋。

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章