RestTemplate:Spring 封裝的 HTTP 同步請求類

目錄

RestTemplate介紹

RestTemplate簡單使用

GET 請求

不帶任何參數 的 GET 請求

帶有參數的 GET 請求

getForEntity()方法

POST 請求

發送 Content-Type 爲 application/x-www-form-urlencoded 的 POST 請求:

發送 Content-Type 爲 application/json 的 POST 請求:

RestTemplate源碼

1.1 默認調用鏈路

1.2 restTemplate->doExecute()

1.3 InterceptingHttpAccessor->getRequestFactory()

1.4 resttemplate->handleResponse()

1.5 HttpMessageConverterExtractor->extractData()

1.6 contentType與messageConverter之間的關係

springboot集成RestTemplate

1.1. 導入依賴:(RestTemplate集成在Web Start中)

1.2. RestTemplat配置:

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

TrackLogClientHttpRequestInterceptor:

HeadClientHttpRequestInterceptor:

自定義異常處理

自定義message轉化器

RestTemplate工具類


RestTemplate介紹

Spring用於同步client端的核心類,簡化了與http服務的通信,並滿足RestFul原則,程序代碼可以給它提供URL,並提取結果。默認情況下,RestTemplate默認依賴jdk的HTTP連接工具。當然你也可以 通過setRequestFactory屬性切換到不同的HTTP源,比如Apache HttpComponentsNettyOkHttp

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響應不成功");
   
}

帶有參數的 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  傳遞參數不成功");
}

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){}

與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) {}
...

HttpStatus.java

public enum HttpStatus {
public boolean is1xxInformational() {}
public boolean is2xxSuccessful() {}
public boolean is3xxRedirection() {}
public boolean is4xxClientError() {}
public boolean is5xxServerError() {}
public boolean isError() {}
}

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);
}

可以看出來,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 請求不成功");
}

發送 Content-Type 爲 application/json 的 POST 請求:

@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源碼

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()

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();
		}
	}
}

1.3 InterceptingHttpAccessor->getRequestFactory()

在默認調用鏈中,InterceptingHttpAccessor的getRequestFactory()方法中,如果沒有設置interceptor攔截器,就返回默認的SimpleClientHttpRequestFactory,反之,返回InterceptingClientHttpRequestFactoryrequestFactory,可以通過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();
		}
	}

然後再調用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);
	}
}

1.4 resttemplate->handleResponse()

在默認調用鏈中,resttemplate的handleResponse,響應處理,包括異常處理,而且異常處理可以通過調用setErrorHandler方法設置自定義的ErrorHandler,實現對請求響應異常的判別和處理。自定義的ErrorHandler需實現ResponseErrorHandler接口,同時Spring boot也提供了默認實現DefaultResponseErrorHandler,因此也可以通過繼承該類來實現自己的ErrorHandler

DefaultResponseErrorHandler默認對40X Bad Request或50X internal異常error等錯誤信息捕捉。如果想捕捉服務本身拋出的異常信息,需要通過自行實現RestTemplateErrorHandler

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);
	}
}

1.5 HttpMessageConverterExtractor->extractData()

在默認調用鏈中, HttpMessageConverterExtractorextractData中進行響應消息體封裝爲java對象,就需要使用message轉換器,可以通過追加的方式增加自定義的messageConverter:先獲取現有的messageConverter,再將自定義的messageConverter添加進去。

根據restTemplatesetMessageConverters的源碼可得,使用追加的方式可防止原有的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);
		}
	}

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);
				}
			}
		}
	}
	.....
}

1.6 contentType與messageConverter之間的關係

HttpMessageConverterExtractorextractData方法中看出,會根據contentTyperesponseClass選擇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 Object application/xml, text/xml, application/*+xml
JavaSerializationConverter Serializable x-java-serialization;charset=UTF-8
FastJsonHttpMessageConverter Object */*

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>

1.2. RestTemplat配置:

  • 使用ClientHttpRequestFactory屬性配置RestTemplat參數,比如ConnectTimeoutReadTimeout;
  • 增加自定義的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;
    }
}

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

自定義interceptor攔截器,實現ClientHttpRequestInterceptor接口

  • 自定義TrackLogClientHttpRequestInterceptor,記錄resttemplaterequestresponse信息,可進行追蹤分析;
  • 自定義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 ========");
    }
}

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;
    }
}

自定義異常處理

可繼承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);
    }

}

自定義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工具類

package com.fly.apigateway;


import com.alibaba.fastjson.JSONObject;
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 高性能請求http https
 * @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 {

    /**
     * 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 {
                result = restTemplate.getForObject(url, String.class, params);
                return result;
            } catch (Exception e) {
                log.error("-----------開始-----------重試count:{} " , i);
                e.printStackTrace();
            }
        }
        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 {
                result = restTemplate.getForObject(url, String.class, params);
                return result;
            } catch (Exception e) {
                log.error("-----------開始-----------重試count:{} " , i);
                e.printStackTrace();
            }
        }
        return result;
    }

    /**
     * http 請求 post
     *
     * @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 {
                result = restTemplate.postForObject(url, requestEntity, String.class);
                return result;
            } catch (Exception e) {
                log.error("-----------開始-----------重試count:{} " , i);
                e.printStackTrace();
            }
        }
        return result;
    }


    /**
     * http 普通請求 post
     * @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); // josn utf-8 格式
        String result = null; // 返回值類型;
        for (int i = 1; i <= retryCount; i++) {
            try {
                result = restTemplate.postForObject(url, requestEntity, String.class);
                return result;
            } catch (Exception e) {
                log.error("-----------開始-----------重試count:{} " , i);
                e.printStackTrace();
            }
        }
        return result;
    }


    /**
     * @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 {
                result = restTemplate.postForObject(url, requestEntity, String.class);
                return result;
            } catch (Exception e) {
                log.error("-----------開始-----------重試count:{} " , i);
                e.printStackTrace();
            }
        }
        return result;
    }


    /**
     * 加密參數類型請求  application/x-www-form-urlencoded
     * MultiValueMap<String, Object>
     * 採用 HttpEntity<MultiValueMap<String, Object>> 構造
     * http 請求 post
     *
     * @param url           地址
     * @param  postParameters 普通post請求需要的參數
     * @param headersMap    header
     * @param connecTimeout 連接時間
     * @param readTimeout   讀取時間
     * @param retryCount    重試機制
     * @return String 類型
     */
    public static String postHttpEncryption(String url, MultiValueMap<String, Object> postParameters, 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<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(postParameters, requestHeaders);
        String result = null; // 返回值類型;
        for (int i = 1; i <= retryCount; i++) {
            try {
                result = restTemplate.postForObject(url, requestEntity, String.class);
                return result;
            } catch (Exception e) {
                log.error("-----------開始-----------重試count:{} " , i);
                e.printStackTrace();
            }
        }
        return result;
    }


    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();
            }
        }
    }


}


參考鏈接

https://juejin.im/post/5db99c285188257e435592ac

https://docs.spring.io/spring-framework/docs/4.3.7.RELEASE/javadoc-api/org/springframework/web/client/RestTemplate.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章