天天看點

RestTemplate:Spring 封裝的 HTTP 同步請求類RestTemplate介紹RestTemplate簡單使用RestTemplate源碼springboot內建RestTemplateRestTemplate工具類

RestTemplate介紹

Spring

用于同步client端的核心類,簡化了與

http

服務的通信,并滿足

RestFul

原則,程式代碼可以給它提供URL,并提取結果。預設情況下,

RestTemplate

預設依賴jdk的HTTP連接配接工具。當然你也可以 通過

setRequestFactory

屬性切換到不同的HTTP源,比如

Apache HttpComponents

Netty

OkHttp

RestTemplate能大幅簡化了送出表單資料的難度,并且附帶了自動轉換JSON資料的功能,但隻有了解了HttpEntity的組成結構(header與body),且了解了與uriVariables之間的差異,才能真正掌握其用法。

其中:

  • RestTemplate

    預設使用

    HttpMessageConverter

    執行個體将

    HTTP

    消息轉換成

    POJO

    或者從

    POJO

    轉換成

    HTTP

    消息。預設情況下會注冊主

    mime

    類型的轉換器,但也可以通過

    setMessageConverters

    注冊自定義轉換器。
  • RestTemplate

    使用了預設的

    DefaultResponseErrorHandler

    ,對40X

    Bad Request

    或50X

    internal

    異常

    error

    等錯誤資訊捕捉。
  • RestTemplate

    還可以使用攔截器

    interceptor

    ,進行對請求連結跟蹤,以及統一head的設定。

RestTemplate

類是在 Spring Framework 3.0 開始引入的,這裡我們使用的 Spring 版本為目前最新的 GA 版本 5.1.6。而在 5.0 以上,官方标注了更推薦使用非阻塞的響應式 HTTP 請求處理類

org.springframework.web.reactive.client.WebClient

來替代

RestTemplate

,尤其是對應異步請求處理的場景上 。

RestTemplate

類提供的 API 有哪些,

RestTemplate

提供了将近 30 個請求方法,其中多數是單個方法重載實作,這裡我主要參考官方文檔

rest-client-access

進行如下分類:

方法 解析
delete() 在特定的URL上對資源執行HTTP DELETE操作
exchange() 在URL上執行特定的HTTP方法,傳回包含對象的ResponseEntity
execute() 在URL上執行特定的HTTP方法,傳回一個從響應體映射得到的對象
getForEntity() 發送一個HTTP GET請求,傳回的ResponseEntity包含了響應體所映射成的對象
getForObject() 發送一個HTTP GET請求,傳回的請求體将映射為一個對象
postForEntity() POST 資料到一個URL,傳回包含一個對象的ResponseEntity
postForObject() POST 資料到一個URL,傳回根據響應體比對形成的對象
headForHeaders() 發送HTTP HEAD請求,傳回包含特定資源URL的HTTP頭
optionsForAllow() 發送HTTP OPTIONS請求,傳回對特定URL的Allow頭資訊
postForLocation() POST 資料到一個URL,傳回新建立資源的URL
put() PUT 資源到特定的URL

RestTemplate簡單使用

GET 請求

不帶任何參數 的 GET 請求

// 一個不帶任何參數 的 GET 請求
@Test
public void testGet_product1() {
   String url = "http://localhost:8080/product/get_product1";
   //方式一:GET 方式擷取 JSON 串資料
   String result = restTemplate.getForObject(url, String.class);
   System.out.println("get_product1傳回結果:" + result);
   Assert.hasText(result, "get_product1傳回結果為空");
    
   //方式二:GET 方式擷取 JSON 資料映射後的 Product 實體對象
   Product product = restTemplate.getForObject(url, Product.class);
   System.out.println("get_product1傳回結果:" + product);
   Assert.notNull(product, "get_product1傳回結果為空");
    
   //方式三:GET 方式擷取包含 Product 實體對象 的響應實體 ResponseEntity 對象,用 getBody() 擷取
   ResponseEntity<Product> responseEntity = restTemplate.getForEntity(url, Product.class);
   System.out.println("get_product1傳回結果:" + responseEntity);
   Assert.isTrue(responseEntity.getStatusCode().equals(HttpStatus.OK), "get_product1響應不成功");
   
}      
RestTemplate:Spring 封裝的 HTTP 同步請求類RestTemplate介紹RestTemplate簡單使用RestTemplate源碼springboot內建RestTemplateRestTemplate工具類

帶有參數的 GET 請求

@Test
public void testGet_product2() {
    String url = "http://localhost:8080/product/get_product2/id={id}";
    
    //方式一:将參數的值存在可變長度參數裡,按照順序進行參數比對
    ResponseEntity<Product> responseEntity = restTemplate.getForEntity(url, Product.class, 101);
    System.out.println(responseEntity);
    Assert.isTrue(responseEntity.getStatusCode().equals(HttpStatus.OK), "get_product2 請求不成功");
    Assert.notNull(responseEntity.getBody().getId(), "get_product2  傳遞參數不成功");
    //方式二:将請求參數以鍵值對形式存儲到 Map 集合中,用于請求時URL上的拼接
    Map<String, Object> uriVariables = new HashMap<>();
    uriVariables.put("id", 101);
    Product result = restTemplate.getForObject(url, Product.class, uriVariables);
    System.out.println(result);
    Assert.notNull(result.getId(), "get_product2  傳遞參數不成功");
}      
RestTemplate:Spring 封裝的 HTTP 同步請求類RestTemplate介紹RestTemplate簡單使用RestTemplate源碼springboot內建RestTemplateRestTemplate工具類

getForEntity()方法

public <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Object... uriVariables){}
public <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Map<String, ?> uriVariables){}
public <T> ResponseEntity<T> getForEntity(URI url, Class<T> responseType){}      
RestTemplate:Spring 封裝的 HTTP 同步請求類RestTemplate介紹RestTemplate簡單使用RestTemplate源碼springboot內建RestTemplateRestTemplate工具類
與getForObject()方法不同的是傳回的是

ResponseEntity

對象,如果需要轉換成pojo,還需要json工具類的引入,這個按個人喜好用。不會解析json的可以百度

FastJson

或者

Jackson

等工具類。然後我們就研究一下

ResponseEntity

下面有啥方法。

ResponseEntity、HttpStatus、BodyBuilder結構

ResponseEntity.java

public HttpStatus getStatusCode(){}
public int getStatusCodeValue(){}
public boolean equals(@Nullable Object other) {}
public String toString() {}
public static BodyBuilder status(HttpStatus status) {}
public static BodyBuilder ok() {}
public static <T> ResponseEntity<T> ok(T body) {}
public static BodyBuilder created(URI location) {}
...      
RestTemplate:Spring 封裝的 HTTP 同步請求類RestTemplate介紹RestTemplate簡單使用RestTemplate源碼springboot內建RestTemplateRestTemplate工具類

HttpStatus.java

public enum HttpStatus {
public boolean is1xxInformational() {}
public boolean is2xxSuccessful() {}
public boolean is3xxRedirection() {}
public boolean is4xxClientError() {}
public boolean is5xxServerError() {}
public boolean isError() {}
}      
RestTemplate:Spring 封裝的 HTTP 同步請求類RestTemplate介紹RestTemplate簡單使用RestTemplate源碼springboot內建RestTemplateRestTemplate工具類

BodyBuilder.java

public interface BodyBuilder extends HeadersBuilder<BodyBuilder> {
    //設定正文的長度,以位元組為機關,由Content-Length标頭
      BodyBuilder contentLength(long contentLength);
    //設定body的MediaType 類型
      BodyBuilder contentType(MediaType contentType);
    //設定響應實體的主體并傳回它。
      <T> ResponseEntity<T> body(@Nullable T body);
}      
RestTemplate:Spring 封裝的 HTTP 同步請求類RestTemplate介紹RestTemplate簡單使用RestTemplate源碼springboot內建RestTemplateRestTemplate工具類
可以看出來,ResponseEntity包含了HttpStatus和BodyBuilder的這些資訊,這更友善我們處理response原生的東西。

POST 請求

發送

Content-Type

application/x-www-form-urlencoded

的 POST 請求:

@Test
public void testPost_product1() {
    String url = "http://localhost:8080/product/post_product1";
    Product product = new Product(201, "Macbook", BigDecimal.valueOf(10000));
      // 設定請求的 Content-Type 為 application/x-www-form-urlencoded
    MultiValueMap<String, String> header = new LinkedMultiValueMap();
    header.add(HttpHeaders.CONTENT_TYPE, (MediaType.APPLICATION_FORM_URLENCODED_VALUE));
    
    //方式二: 将請求參數值以 K=V 方式用 & 拼接,發送請求使用
    String productStr = "id=" + product.getId() + "&name=" + product.getName() + "&price=" + product.getPrice();
    HttpEntity<String> request = new HttpEntity<>(productStr, header);
    ResponseEntity<String> exchangeResult = restTemplate.exchange(url, HttpMethod.POST, request, String.class);
    System.out.println("post_product1: " + exchangeResult);
    Assert.isTrue(exchangeResult.getStatusCode().equals(HttpStatus.OK), "post_product1 請求不成功");
    //方式一: 将請求參數以鍵值對形式存儲在 MultiValueMap 集合,發送請求時使用
    MultiValueMap<String, Object> map = new LinkedMultiValueMap();
    map.add("id", (product.getId()));
    map.add("name", (product.getName()));
    map.add("price", (product.getPrice()));
    HttpEntity<MultiValueMap> request2 = new HttpEntity<>(map, header);
    ResponseEntity<String> exchangeResult2 = restTemplate.exchange(url, HttpMethod.POST, request2, String.class);
    System.out.println("post_product1: " + exchangeResult2);
    Assert.isTrue(exchangeResult.getStatusCode().equals(HttpStatus.OK), "post_product1 請求不成功");
}      
RestTemplate:Spring 封裝的 HTTP 同步請求類RestTemplate介紹RestTemplate簡單使用RestTemplate源碼springboot內建RestTemplateRestTemplate工具類

Content-Type

application/json

@Test
public void testPost_product2() {
    String url = "http://localhost:8080/product/post_product2";
    
    // 設定請求的 Content-Type 為 application/json
    MultiValueMap<String, String> header = new LinkedMultiValueMap();
    header.put(HttpHeaders.CONTENT_TYPE, Arrays.asList(MediaType.APPLICATION_JSON_VALUE));
    // 設定 Accept 向伺服器表明用戶端可處理的内容類型
    header.put(HttpHeaders.ACCEPT, Arrays.asList(MediaType.APPLICATION_JSON_VALUE));
    // 直接将實體 Product 作為請求參數傳入,底層利用 Jackson 架構序列化成 JSON 串發送請求
    HttpEntity<Product> request = new HttpEntity<>(new Product(2, "Macbook", BigDecimal.valueOf(10000)), header);
    ResponseEntity<String> exchangeResult = restTemplate.exchange(url, HttpMethod.POST, request, String.class);
    System.out.println("post_product2: " + exchangeResult);
    Assert.isTrue(exchangeResult.getStatusCode().equals(HttpStatus.OK), "post_product2 請求不成功");
}      
RestTemplate:Spring 封裝的 HTTP 同步請求類RestTemplate介紹RestTemplate簡單使用RestTemplate源碼springboot內建RestTemplateRestTemplate工具類

RestTemplate源碼

1.1 預設調用鍊路

restTemplate

進行API調用時,預設調用鍊:

###########1.使用createRequest建立請求########
resttemplate->execute()->doExecute()
HttpAccessor->createRequest()
//擷取攔截器Interceptor,InterceptingClientHttpRequestFactory,SimpleClientHttpRequestFactory
InterceptingHttpAccessor->getRequestFactory() 
//擷取預設的SimpleBufferingClientHttpRequest
SimpleClientHttpRequestFactory->createRequest()
#######2.擷取響應response進行處理###########
AbstractClientHttpRequest->execute()->executeInternal()
AbstractBufferingClientHttpRequest->executeInternal()
###########3.異常處理#####################
resttemplate->handleResponse()
##########4.響應消息體封裝為java對象#######
HttpMessageConverterExtractor->extractData()      
RestTemplate:Spring 封裝的 HTTP 同步請求類RestTemplate介紹RestTemplate簡單使用RestTemplate源碼springboot內建RestTemplateRestTemplate工具類

1.2 restTemplate->doExecute()

在預設調用鍊中,

restTemplate

進行API調用都會調用

doExecute

方法,此方法主要可以進行如下步驟:

1)使用

createRequest

建立請求,擷取響應

2)判斷響應是否異常,處理異常

3)将響應消息體封裝為java對象

@Nullable
protected <T> T doExecute(URI url, @Nullable HttpMethod method, @Nullable RequestCallback requestCallback,
        @Nullable ResponseExtractor<T> responseExtractor) throws RestClientException {
    Assert.notNull(url, "URI is required");
    Assert.notNull(method, "HttpMethod is required");
    ClientHttpResponse response = null;
    try {
        //使用createRequest建立請求
        ClientHttpRequest request = createRequest(url, method);
        if (requestCallback != null) {
            requestCallback.doWithRequest(request);
        }
        //擷取響應response進行處理
        response = request.execute();
        //異常處理
        handleResponse(url, method, response);
        //響應消息體封裝為java對象
        return (responseExtractor != null ? responseExtractor.extractData(response) : null);
    }catch (IOException ex) {
        String resource = url.toString();
        String query = url.getRawQuery();
        resource = (query != null ? resource.substring(0, resource.indexOf('?')) : resource);
        throw new ResourceAccessException("I/O error on " + method.name() +
                " request for \"" + resource + "\": " + ex.getMessage(), ex);
    }finally {
        if (response != null) {
            response.close();
        }
    }
}      
RestTemplate:Spring 封裝的 HTTP 同步請求類RestTemplate介紹RestTemplate簡單使用RestTemplate源碼springboot內建RestTemplateRestTemplate工具類

1.3 InterceptingHttpAccessor->getRequestFactory()

InterceptingHttpAccessor的getRequestFactory()

方法中,如果沒有設定

interceptor

攔截器,就傳回預設的

SimpleClientHttpRequestFactory

,反之,傳回

InterceptingClientHttpRequestFactory

requestFactory

,可以通過

resttemplate.setInterceptors

設定自定義攔截器

interceptor

//Return the request factory that this accessor uses for obtaining client request handles.
public ClientHttpRequestFactory getRequestFactory() {
        //擷取攔截器interceptor(自定義的)
        List<ClientHttpRequestInterceptor> interceptors = getInterceptors();
        if (!CollectionUtils.isEmpty(interceptors)) {
            ClientHttpRequestFactory factory = this.interceptingRequestFactory;
            if (factory == null) {
                factory = new InterceptingClientHttpRequestFactory(super.getRequestFactory(), interceptors);
                this.interceptingRequestFactory = factory;
            }
            return factory;
        }
        else {
            return super.getRequestFactory();
        }
    }      
RestTemplate:Spring 封裝的 HTTP 同步請求類RestTemplate介紹RestTemplate簡單使用RestTemplate源碼springboot內建RestTemplateRestTemplate工具類

然後再調用

SimpleClientHttpRequestFactory的createRequest

建立連接配接:

@Override
public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException {
    HttpURLConnection connection = openConnection(uri.toURL(), this.proxy);
    prepareConnection(connection, httpMethod.name());
    if (this.bufferRequestBody) {
        return new SimpleBufferingClientHttpRequest(connection, this.outputStreaming);
    }
    else {
        return new SimpleStreamingClientHttpRequest(connection, this.chunkSize, this.outputStreaming);
    }
}      
RestTemplate:Spring 封裝的 HTTP 同步請求類RestTemplate介紹RestTemplate簡單使用RestTemplate源碼springboot內建RestTemplateRestTemplate工具類

1.4 resttemplate->handleResponse()

resttemplate的handleResponse

,響應處理,包括異常處理,而且異常處理可以通過調用

setErrorHandler

方法設定自定義的

ErrorHandler

,實作對請求響應異常的判别和處理。自定義的

ErrorHandler

需實作

ResponseErrorHandler

接口,同時

Spring boot

也提供了預設實作

DefaultResponseErrorHandler

,是以也可以通過繼承該類來實作自己的

ErrorHandler

DefaultResponseErrorHandler

預設對40X

Bad Request

internal

error

等錯誤資訊捕捉。如果想捕捉服務本身抛出的異常資訊,需要通過自行實作

RestTemplate

ErrorHandler

ResponseErrorHandler errorHandler = getErrorHandler();
               //判斷響應是否有異常
    boolean hasError = errorHandler.hasError(response);
    if (logger.isDebugEnabled()) {
        try {
            int code = response.getRawStatusCode();
            HttpStatus status = HttpStatus.resolve(code);
            logger.debug("Response " + (status != null ? status : code));
        }catch (IOException ex) {
            // ignore
        }
    }
    //有異常進行異常處理
    if (hasError) {
        errorHandler.handleError(url, method, response);
    }
}      
RestTemplate:Spring 封裝的 HTTP 同步請求類RestTemplate介紹RestTemplate簡單使用RestTemplate源碼springboot內建RestTemplateRestTemplate工具類

1.5 HttpMessageConverterExtractor->extractData()

HttpMessageConverterExtractor

extractData

中進行響應消息體封裝為

java

對象,就需要使用

message

轉換器,可以通過追加的方式增加自定義的

messageConverter

:先擷取現有的

messageConverter

,再将自定義的

messageConverter

添加進去。

根據

restTemplate

setMessageConverters

的源碼可得,使用追加的方式可防止原有的

messageConverter

丢失,源碼:

public void setMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
        //檢驗
        validateConverters(messageConverters);
        // Take getMessageConverters() List as-is when passed in here
        if (this.messageConverters != messageConverters) {
            //先清除原有的messageConverter
            this.messageConverters.clear();
            //後加載重新定義的messageConverter
            this.messageConverters.addAll(messageConverters);
        }
    }      
RestTemplate:Spring 封裝的 HTTP 同步請求類RestTemplate介紹RestTemplate簡單使用RestTemplate源碼springboot內建RestTemplateRestTemplate工具類

HttpMessageConverterExtractor的extractData

源碼:

MessageBodyClientHttpResponseWrapper responseWrapper = new MessageBodyClientHttpResponseWrapper(response);
    if (!responseWrapper.hasMessageBody() || responseWrapper.hasEmptyMessageBody()) {
        return null;
    }
    //擷取到response的ContentType類型
    MediaType contentType = getContentType(responseWrapper);
    try {
        //依次循環messageConverter進行判斷是否符合轉換條件,進行轉換java對象
        for (HttpMessageConverter<?> messageConverter : this.messageConverters) {
        //會根據設定的傳回類型responseType和contentType參數進行比對,選擇合适的MessageConverter
            if (messageConverter instanceof GenericHttpMessageConverter) {
                GenericHttpMessageConverter<?> genericMessageConverter =
                        (GenericHttpMessageConverter<?>) messageConverter;
                if (genericMessageConverter.canRead(this.responseType, null, contentType)) {
                    if (logger.isDebugEnabled()) {
                        ResolvableType resolvableType = ResolvableType.forType(this.responseType);
                        logger.debug("Reading to [" + resolvableType + "]");
                    }
                    return (T) genericMessageConverter.read(this.responseType, null, responseWrapper);
                }
            }
            if (this.responseClass != null) {
                if (messageConverter.canRead(this.responseClass, contentType)) {
                    if (logger.isDebugEnabled()) {
                        String className = this.responseClass.getName();
                        logger.debug("Reading to [" + className + "] as \"" + contentType + "\"");
                    }
                    return (T) messageConverter.read((Class) this.responseClass, responseWrapper);
                }
            }
        }
    }
    .....
}      
RestTemplate:Spring 封裝的 HTTP 同步請求類RestTemplate介紹RestTemplate簡單使用RestTemplate源碼springboot內建RestTemplateRestTemplate工具類

1.6 contentType與messageConverter之間的關系

HttpMessageConverterExtractor

extractData

方法中看出,會根據

contentType

responseClass

選擇

messageConverter

是否可讀、消息轉換。關系如下:

類名 支援的JavaType 支援的MediaType
ByteArrayHttpMessageConverter byte[] application/octet-stream, */*
StringHttpMessageConverter String text/plain, */*
ResourceHttpMessageConverter Resource */*
SourceHttpMessageConverter Source application/xml, text/xml, application/*+xml
AllEncompassingFormHttpMessageConverter Map<K, List<?>> application/x-www-form-urlencoded, multipart/form-data
MappingJackson2HttpMessageConverter Object application/json, application/*+json
Jaxb2RootElementHttpMessageConverter
JavaSerializationConverter Serializable x-java-serialization;charset=UTF-8
FastJsonHttpMessageConverter

springboot內建RestTemplate

  根據上述源碼的分析學習,可以輕松,簡單地在項目進行對RestTemplate進行優雅地使用,比如增加自定義的異常處理、

MessageConverter

以及攔截器

interceptor

。本文使用示例

demo

,詳情請檢視接下來的内容。

1.1. 導入依賴:(RestTemplate內建在Web Start中)

<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
  <version>2.2.0.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
  <groupId>org.projectlombok</groupId>
  <artifactId>lombok</artifactId>
  <version>1.18.10</version>
  <scope>provided</scope>
</dependency>      
RestTemplate:Spring 封裝的 HTTP 同步請求類RestTemplate介紹RestTemplate簡單使用RestTemplate源碼springboot內建RestTemplateRestTemplate工具類

1.2. RestTemplat配置:

  • 使用

    ClientHttpRequestFactory

    屬性配置RestTemplat參數,比如

    ConnectTimeout

    ReadTimeout

    ;
  • 增加自定義的

    interceptor

    攔截器和異常處理;
  • 追加

    message

    轉換器;
  • 配置自定義的異常處理.
@Configuration
public class RestTemplateConfig {
    @Value("${resttemplate.connection.timeout}")
    private int restTemplateConnectionTimeout;
    @Value("${resttemplate.read.timeout}")
    private int restTemplateReadTimeout;
    @Bean
    //@LoadBalanced
    public RestTemplate restTemplate( ClientHttpRequestFactory simleClientHttpRequestFactory) {
        RestTemplate restTemplate = new RestTemplate();
        //配置自定義的message轉換器
        List<HttpMessageConverter<?>> messageConverters = restTemplate.getMessageConverters();
        messageConverters.add(new CustomMappingJackson2HttpMessageConverter());
        restTemplate.setMessageConverters(messageConverters);
        //配置自定義的interceptor攔截器
        List<ClientHttpRequestInterceptor> interceptors=new ArrayList<ClientHttpRequestInterceptor>();
        interceptors.add(new HeadClientHttpRequestInterceptor());
        interceptors.add(new TrackLogClientHttpRequestInterceptor());
        restTemplate.setInterceptors(interceptors);
        //配置自定義的異常處理
        restTemplate.setErrorHandler(new CustomResponseErrorHandler());
        restTemplate.setRequestFactory(simleClientHttpRequestFactory);
        return restTemplate;
    }
    @Bean
    public ClientHttpRequestFactory simleClientHttpRequestFactory(){
        SimpleClientHttpRequestFactory reqFactory= new SimpleClientHttpRequestFactory();
        reqFactory.setConnectTimeout(restTemplateConnectionTimeout);
        reqFactory.setReadTimeout(restTemplateReadTimeout);
        return reqFactory;
    }
}      
RestTemplate:Spring 封裝的 HTTP 同步請求類RestTemplate介紹RestTemplate簡單使用RestTemplate源碼springboot內建RestTemplateRestTemplate工具類

1.3. 元件(自定義異常處理、interceptor攔截器、message轉化器)

自定義

interceptor

攔截器,實作

ClientHttpRequestInterceptor

接口
  • TrackLogClientHttpRequestInterceptor

    ,記錄

    resttemplate

    request

    response

    資訊,可進行追蹤分析;
  • HeadClientHttpRequestInterceptor

    ,設定請求頭的參數。API發送各種請求,很多請求都需要用到相似或者相同的Http Header。如果在每次請求之前都把

    Header

    填入

    HttpEntity/RequestEntity

    ,這樣的代碼會顯得十分備援,可以在攔截器統一設定。

TrackLogClientHttpRequestInterceptor:

/**
 * @Date: 2019/10/25 22:48,記錄resttemplate通路資訊
 * @Description:   記錄resttemplate通路資訊
 */
@Slf4j
public class TrackLogClientHttpRequestInterceptor implements ClientHttpRequestInterceptor {
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
        trackRequest(request,body);
        ClientHttpResponse httpResponse = execution.execute(request, body);
        trackResponse(httpResponse);
        return httpResponse;
    }
    private void trackResponse(ClientHttpResponse httpResponse)throws IOException {
        log.info("============================response begin==========================================");
        log.info("Status code  : {}", httpResponse.getStatusCode());
        log.info("Status text  : {}", httpResponse.getStatusText());
        log.info("Headers      : {}", httpResponse.getHeaders());
        log.info("=======================response end=================================================");
    }
    private void trackRequest(HttpRequest request, byte[] body)throws UnsupportedEncodingException {
        log.info("======= request begin ========");
        log.info("uri : {}", request.getURI());
        log.info("method : {}", request.getMethod());
        log.info("headers : {}", request.getHeaders());
        log.info("request body : {}", new String(body, "UTF-8"));
        log.info("======= request end ========");
    }
}      
RestTemplate:Spring 封裝的 HTTP 同步請求類RestTemplate介紹RestTemplate簡單使用RestTemplate源碼springboot內建RestTemplateRestTemplate工具類

HeadClientHttpRequestInterceptor:

@Slf4j
public class HeadClientHttpRequestInterceptor implements ClientHttpRequestInterceptor {
    public ClientHttpResponse intercept(HttpRequest httpRequest, byte[] bytes, ClientHttpRequestExecution clientHttpRequestExecution) throws IOException {
       log.info("#####head handle########");
        HttpHeaders headers = httpRequest.getHeaders();
        headers.add("Accept", "application/json");
        headers.add("Accept-Encoding", "gzip");
        headers.add("Content-Encoding", "UTF-8");
        headers.add("Content-Type", "application/json; charset=UTF-8");
        ClientHttpResponse response = clientHttpRequestExecution.execute(httpRequest, bytes);
        HttpHeaders headersResponse = response.getHeaders();
        headersResponse.add("Accept", "application/json");
        return  response;
    }
}      
RestTemplate:Spring 封裝的 HTTP 同步請求類RestTemplate介紹RestTemplate簡單使用RestTemplate源碼springboot內建RestTemplateRestTemplate工具類

自定義異常處理

可繼承

DefaultResponseErrorHandler

或者實作

ResponseErrorHandler

接口:

  • 實作自定義

    ErrorHandler

    的思路是根據響應消息體進行相應的異常處理政策,對于其他異常情況由父類

    DefaultResponseErrorHandler

    來進行處理。
  • CustomResponseErrorHandler

    進行30x異常處理

CustomResponseErrorHandler:

/**
 * @Description:  30X的異常處理
 */
@Slf4j
public class CustomResponseErrorHandler extends DefaultResponseErrorHandler {
    @Override
    public boolean hasError(ClientHttpResponse response) throws IOException {
        HttpStatus statusCode = response.getStatusCode();
        if(statusCode.is3xxRedirection()){
            return true;
        }
        return super.hasError(response);
    }
    @Override
    public void handleError(ClientHttpResponse response) throws IOException {
        HttpStatus statusCode = response.getStatusCode();
        if(statusCode.is3xxRedirection()){
            log.info("########30X錯誤,需要重定向!##########");
            return;
        }
        super.handleError(response);
    }
}      
RestTemplate:Spring 封裝的 HTTP 同步請求類RestTemplate介紹RestTemplate簡單使用RestTemplate源碼springboot內建RestTemplateRestTemplate工具類

自定義message轉化器

/**
 * @Description: 将Content-Type:"text/html"轉換為Map類型格式
 */
public class CustomMappingJackson2HttpMessageConverter extends MappingJackson2HttpMessageConverter {
    public CustomMappingJackson2HttpMessageConverter() {
        List<MediaType> mediaTypes = new ArrayList<MediaType>();
        mediaTypes.add(MediaType.TEXT_PLAIN);
        mediaTypes.add(MediaType.TEXT_HTML);  //加入text/html類型的支援
        setSupportedMediaTypes(mediaTypes);// tag6
    }
}      
RestTemplate:Spring 封裝的 HTTP 同步請求類RestTemplate介紹RestTemplate簡單使用RestTemplate源碼springboot內建RestTemplateRestTemplate工具類

RestTemplate工具類

package com.fly.apigateway;
package com.devicemag.core.utils;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.ResponseErrorHandler;
import org.springframework.web.client.RestTemplate;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.Set;
/**
 * @Title: restTemplateUtils
 * @ClassName: com.fly.apigateway.RestTemplateUtils.java
 * @Description:
 *
 * @Copyright 2016-2019  - Powered By 研發中心
 * @author: 王延飛
 * @date:  2020/4/10 18:25
 * @version V1.0
 */
@Slf4j
public class RestTemplateUtils {
    /**
     * 讀取時間,自定義預設8s,0表示沒有逾時時間
     */
    public static final int READ_TIMEOUT = 1000*8;
    /**
     * 連接配接時間,自定義預設8s,0表示沒有逾時時間
     */
    public static final int CONNEC_TIMEOUT = 1000*8;
    /**
     * 重試次數,自定義預設1
     */
    public static final int RETRY_COUNT = 1;
    /**
     * http 請求 GET
     *
     * @param url           位址
     * @param params        參數
     * @return String 類型
     */
    public static String getHttp(String url, JSONObject params) {
        String result = getHttp(url, params, READ_TIMEOUT, CONNEC_TIMEOUT, RETRY_COUNT);
        return result;
    }
    /**
     * http 請求 GET
     *
     * @param url           位址
     * @param params        參數
     * @param connecTimeout 連接配接時間
     * @param readTimeout   讀取時間
     * @param retryCount    重試機制
     * @return String 類型
     */
    public static String getHttp(String url, JSONObject params, int connecTimeout, int readTimeout, int retryCount) {
        SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
        requestFactory.setConnectTimeout(connecTimeout);
        requestFactory.setReadTimeout(readTimeout);
        RestTemplate restTemplate = new RestTemplate(requestFactory);
        restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8)); // 設定編碼集
        restTemplate.setErrorHandler(new DefaultResponseErrorHandler()); // 異常處理
        url = expandURL(url, params);
        String result = null; // 傳回值類型;
        for (int i = 1; i <= retryCount; i++) {
            try {
                log.info("【GET/HTTP請求資訊】,請求位址:{},請求參數:{}", url, params);
                result = restTemplate.getForObject(url, String.class, params);
                log.info("【GET/HTTP請求資訊】,請求位址:{},請求參數:{},傳回結果:{}", url, params,result);
                return result;
            } catch (Exception e) {
                log.error("【GET/HTTP請求資訊】異常,重試count:{},請求位址:{},請求參數:{},異常資訊:{}", i, url, params,e);
                e.printStackTrace();
            }
        }
        return result;
    }
    /**
     * https 請求 GET
     *
     * @param url           位址
     * @param params        參數
     * @return String 類型
     */
    public static String getHttps(String url, JSONObject params) {
        String result = getHttps(url, params, READ_TIMEOUT, CONNEC_TIMEOUT, RETRY_COUNT);
        return result;
    }
    /**
     * https 請求 GET
     *
     * @param url           位址
     * @param params        參數
     * @param connecTimeout 連接配接時間
     * @param readTimeout   讀取時間
     * @param retryCount    重試機制
     * @return String 類型
     */
    public static String getHttps(String url, JSONObject params, int connecTimeout, int readTimeout, int retryCount) {
        SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
        requestFactory.setConnectTimeout(connecTimeout);
        requestFactory.setReadTimeout(readTimeout);
        RestTemplate restTemplate = new RestTemplate(requestFactory);
        restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8)); // 設定編碼集
        restTemplate.setErrorHandler(new DefaultResponseErrorHandler()); //error處理
        restTemplate.setRequestFactory(new HttpsClientRequestFactory()); // 繞過https
        url = expandURL(url, params);
        String result = null; // 傳回值類型;
        for (int i = 1; i <= retryCount; i++) {
            try {
                log.info("【GET/HTTPS請求資訊】,請求位址:{},請求參數:{}", url, params);
                result = restTemplate.getForObject(url, String.class, params);
                log.info("【GET/HTTPS請求資訊】,請求位址:{},請求參數:{},傳回結果:{}", url, params,result);
                return result;
            } catch (Exception e) {
                log.error("【GET/HTTPS請求資訊】異常,重試count:{},請求位址:{},請求參數:{},異常資訊:{}", i, url, params,e);
                e.printStackTrace();
            }
        }
        return result;
    }
    /**
     * http 請求 post/JSON
     *
     * @param url           位址
     * @param params        參數
     * @return String 類型
     */
    public static String postHttp(String url, JSONObject params, Map headersMap) {
        String result = postHttp(url, params,headersMap, READ_TIMEOUT, CONNEC_TIMEOUT, RETRY_COUNT);
        return result;
    }
    /**
     * http請求 post/JSON
     *
     * @param url           位址
     * @param params        參數
     * @param headersMap    header
     * @param connecTimeout 連接配接時間
     * @param readTimeout   讀取時間
     * @param retryCount    重試機制
     * @return String 類型
     */
    public static String postHttp(String url, JSONObject params, Map headersMap, int connecTimeout, int readTimeout, int retryCount) {
        SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory(); // 時間函數
        requestFactory.setConnectTimeout(connecTimeout);
        requestFactory.setReadTimeout(readTimeout);
        //内部實際實作為 HttpClient
        RestTemplate restTemplate = new RestTemplate(requestFactory);
        restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8)); // 設定編碼集
        restTemplate.setErrorHandler(new DefaultResponseErrorHandler()); // 異常處理的headers error 處理
        // 設定·header資訊
        HttpHeaders requestHeaders = new HttpHeaders();
        requestHeaders.setAll(headersMap);
        HttpEntity<JSONObject> requestEntity = new HttpEntity<JSONObject>(params, requestHeaders); // josn utf-8 格式
        String result = null; // 傳回值類型;
        for (int i = 1; i <= retryCount; i++) {
            try {
                log.info("【POST/HTTP請求資訊】,請求位址:{},請求參數:{}", url, params);
                result = restTemplate.postForObject(url, requestEntity, String.class);
                log.info("【POST/HTTP請求資訊】,請求位址:{},請求參數:{},傳回結果:{}", url, params,result);
                return result;
            } catch (Exception e) {
                log.error("【POST/HTTP請求資訊】異常,重試count:{},請求位址:{},請求參數:{},異常資訊:{}", i, url, params,e);
                e.printStackTrace();
            }
        }
        return result;
    }
    /**
     * http請求 post/MAP
     *
     * @param url           位址
     * @param params        參數
     * @return String 類型
     */
    public static String postHttp(String url, MultiValueMap params, Map headersMap) {
        String result = postHttp(url, params,headersMap, READ_TIMEOUT, CONNEC_TIMEOUT, RETRY_COUNT);
        return result;
    }
    /**
     * http 普通請求 post/MAP
     * @param url           位址
     * @param params         MultiValueMap<String, String> paramMap = new LinkedMultiValueMap<>();
     * @param headersMap    header
     * @param connecTimeout 連接配接時間
     * @param readTimeout   讀取時間
     * @param retryCount    重試機制
     * @return String 類型
     */
    public static String postHttp(String url, MultiValueMap params, Map headersMap, int connecTimeout, int readTimeout, int retryCount) {
        SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory(); // 時間函數
        requestFactory.setConnectTimeout(connecTimeout);
        requestFactory.setReadTimeout(readTimeout);
        //内部實際實作為 HttpClient
        RestTemplate restTemplate = new RestTemplate(requestFactory);
        restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8)); // 設定編碼集
        restTemplate.setErrorHandler(new DefaultResponseErrorHandler()); // 異常處理的headers error 處理
        // 設定·header資訊
        HttpHeaders requestHeaders = new HttpHeaders();
        requestHeaders.setAll(headersMap);
        HttpEntity<Map> requestEntity = new HttpEntity<Map>(params, requestHeaders); // json utf-8 格式
        String result = null; // 傳回值類型;
        for (int i = 1; i <= retryCount; i++) {
            try {
                log.info("【POST/HTTP請求資訊】,請求位址:{},請求參數:{}", url, params);
                result = restTemplate.postForObject(url, requestEntity, String.class);
                log.info("【POST/HTTP請求資訊】,請求位址:{},請求參數:{},傳回結果:{}", url, params,result);
                return result;
            } catch (Exception e) {
                log.error("【POST/HTTP請求資訊】異常,重試count:{},請求位址:{},請求參數:{},異常資訊:{}", i, url, params,e);
                e.printStackTrace();
            }
        }
        return result;
    }
    /**
     * https 普通請求 post/JSON
     *
     * @param url           位址
     * @param params        參數
     * @return String 類型
     */
    public static String postHttps(String url, JSONObject params, Map headersMap) {
        String result = postHttps(url, params,headersMap, READ_TIMEOUT, CONNEC_TIMEOUT, RETRY_COUNT);
        return result;
    }
    /**
     * https 普通請求 post/JSON
     * @param url        請求位址
     * @param params     請求 josn 格式參數
     * @param headersMap headers 頭部需要參數
     * @param retryCount 重試機制
     * @return 傳回string類型傳回值
     */
    public static String postHttps(String url, JSONObject params, Map headersMap, int connecTimeout, int readTimeout, int retryCount) {
        SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory(); // 時間函數
        requestFactory.setConnectTimeout(connecTimeout);
        requestFactory.setReadTimeout(readTimeout);
        //内部實際實作為 HttpClient
        RestTemplate restTemplate = new RestTemplate(requestFactory);
        restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8)); // 設定編碼集
        restTemplate.setRequestFactory(new HttpsClientRequestFactory()); // 繞過https
        restTemplate.setErrorHandler(new DefaultResponseErrorHandler()); // 異常處理的headers error 處理
        // 設定·header資訊
        HttpHeaders requestHeaders = new HttpHeaders();
        requestHeaders.setAll(headersMap);
        HttpEntity<JSONObject> requestEntity = new HttpEntity<JSONObject>(params, requestHeaders); // josn utf-8 格式
        String result = null; // 傳回值類型;
        for (int i = 1; i <= retryCount; i++) {
            try {
                log.info("【POST/HTTPS請求資訊】,請求位址:{},請求參數:{}", url, params);
                result = restTemplate.postForObject(url, requestEntity, String.class);
                log.info("【POST/HTTPS請求資訊】,請求位址:{},請求參數:{},傳回結果:{}", url, params,result);
                return result;
            } catch (Exception e) {
                log.error("【POST/HTTPS請求資訊】異常,重試count:{},請求位址:{},請求參數:{},異常資訊:{}", i, url, params,e);
                e.printStackTrace();
            }
        }
        return result;
    }
    /**
     * @Title: URL拼接
     * @MethodName:  expandURL
     * @param url
     * @param jsonObject
     * @Return java.lang.String
     * @Exception
     * @Description:
     *
     * @author: 王延飛
     * @date:  2021/1/4 15:30
     */
    private static String expandURL(String url,JSONObject jsonObject) {
        StringBuilder sb = new StringBuilder(url);
        sb.append("?");
        Set<String> keys = jsonObject.keySet();
        for (String key : keys) {
            sb.append(key).append("=").append(jsonObject.getString(key)).append("&");
        }
        return sb.deleteCharAt(sb.length() - 1).toString();
    }
    /**
     * 出現異常,可自定義
     */
    private static class DefaultResponseErrorHandler implements ResponseErrorHandler {
        /**
         * 對response進行判斷,如果是異常情況,傳回true
         */
        @Override
        public boolean hasError(ClientHttpResponse response) throws IOException {
            return response.getStatusCode().value() != HttpServletResponse.SC_OK;
        }
        /**
         * 異常情況時的處理方法
         */
        @Override
        public void handleError(ClientHttpResponse response) throws IOException {
            BufferedReader br = new BufferedReader(new InputStreamReader(response.getBody()));
            StringBuilder sb = new StringBuilder();
            String str = null;
            while ((str = br.readLine()) != null) {
                sb.append(str);
            }
            try {
                throw new Exception(sb.toString());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}      
RestTemplate:Spring 封裝的 HTTP 同步請求類RestTemplate介紹RestTemplate簡單使用RestTemplate源碼springboot內建RestTemplateRestTemplate工具類
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import javax.net.ssl.*;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.Socket;
import java.security.cert.X509Certificate;
/**
 * @Title: Https 繞過
 * @Description:
 *
 * @Copyright 2020-2021  - Powered By 研發中心
 * @author: 王延飛
 * @date:  2020/9/10 0010 9:01
 * @version V1.0
 */
public class HttpsClientRequestFactory extends SimpleClientHttpRequestFactory {
    @Override
    protected void prepareConnection(HttpURLConnection connection, String httpMethod) {
        try {
            if (!(connection instanceof HttpsURLConnection)) {
                throw new RuntimeException("An instance of HttpsURLConnection is expected");
            }
            HttpsURLConnection httpsConnection = (HttpsURLConnection) connection;
            TrustManager[] trustAllCerts = new TrustManager[]{
                    new X509TrustManager() {
                        @Override
                        public X509Certificate[] getAcceptedIssuers() {
                            return null;
                        }
                        @Override
                        public void checkClientTrusted(X509Certificate[] certs, String authType) {
                        }
                        @Override
                        public void checkServerTrusted(X509Certificate[] certs, String authType) {
                        }
                    }
            };
            SSLContext sslContext = SSLContext.getInstance("TLS");
            sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
            httpsConnection.setSSLSocketFactory(new MyCustomSSLSocketFactory(sslContext.getSocketFactory()));
            httpsConnection.setHostnameVerifier(new HostnameVerifier() {
                @Override
                public boolean verify(String s, SSLSession sslSession) {
                    return true;
                }
            });
            super.prepareConnection(httpsConnection, httpMethod);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    /**
     * We need to invoke sslSocket.setEnabledProtocols(new String[] {"SSLv3"});
     * see http://www.oracle.com/technetwork/java/javase/documentation/cve-2014-3566-2342133.html (Java 8 section)
     */
    // SSLSocketFactory用于建立 SSLSockets
    private static class MyCustomSSLSocketFactory extends SSLSocketFactory {
        private final SSLSocketFactory delegate;
        public MyCustomSSLSocketFactory(SSLSocketFactory delegate) {
            this.delegate = delegate;
        }
        // 傳回預設啟用的密碼套件。除非一個清單啟用,對SSL連接配接的握手會使用這些密碼套件。
        // 這些預設的服務的最低品質要求保密保護和伺服器身份驗證
        @Override
        public String[] getDefaultCipherSuites() {
            return delegate.getDefaultCipherSuites();
        }
        // 傳回的密碼套件可用于SSL連接配接啟用的名字
        @Override
        public String[] getSupportedCipherSuites() {
            return delegate.getSupportedCipherSuites();
        }
        @Override
        public Socket createSocket(final Socket socket, final String host, final int port,
                                   final boolean autoClose) throws IOException {
            final Socket underlyingSocket = delegate.createSocket(socket, host, port, autoClose);
            return overrideProtocol(underlyingSocket);
        }
        @Override
        public Socket createSocket(final String host, final int port) throws IOException {
            final Socket underlyingSocket = delegate.createSocket(host, port);
            return overrideProtocol(underlyingSocket);
        }
        @Override
        public Socket createSocket(final String host, final int port, final InetAddress localAddress,
                                   final int localPort) throws
                IOException {
            final Socket underlyingSocket = delegate.createSocket(host, port, localAddress, localPort);
            return overrideProtocol(underlyingSocket);
        }
        @Override
        public Socket createSocket(final InetAddress host, final int port) throws IOException {
            final Socket underlyingSocket = delegate.createSocket(host, port);
            return overrideProtocol(underlyingSocket);
        }
        @Override
        public Socket createSocket(final InetAddress host, final int port, final InetAddress localAddress,
                                   final int localPort) throws
                IOException {
            final Socket underlyingSocket = delegate.createSocket(host, port, localAddress, localPort);
            return overrideProtocol(underlyingSocket);
        }
        private Socket overrideProtocol(final Socket socket) {
            if (!(socket instanceof SSLSocket)) {
                throw new RuntimeException("An instance of SSLSocket is expected");
            }
            ((SSLSocket) socket).setEnabledProtocols(new String[]{"TLSv1"});
            return socket;
        }
    }
}      
RestTemplate:Spring 封裝的 HTTP 同步請求類RestTemplate介紹RestTemplate簡單使用RestTemplate源碼springboot內建RestTemplateRestTemplate工具類

參考連結

https://juejin.im/post/5db99c285188257e435592ac https://docs.spring.io/spring-framework/docs/4.3.7.RELEASE/javadoc-api/org/springframework/web/client/RestTemplate.html