Spring Boot使用RestTemplate消費REST服務的幾個問題記錄
我們可以通過Spring Boot快速開發REST接口,同時也可能需要在實現接口的過程中,通過Spring Boot調用內外部REST接口完成業務邏輯。
在Spring Boot中,調用REST Api常見的一般主要有兩種方式,通過自帶的RestTemplate或者自己開發http客戶端工具實現服務調用。
RestTemplate基本功能非常強大,不過某些特殊場景,我們可能還是更習慣用自己封裝的工具類,比如上傳文件至分布式文件系統、處理帶證書的https請求等。
本文以RestTemplate來舉例,記錄幾個使用RestTemplate調用接口過程中發現的問題和解決方案。
一、RestTemplate簡介
1、什么是RestTemplate
我們自己封裝的HttpClient,通常都會有一些模板代碼,比如建立連接,構造請求頭和請求體,然后根據響應,解析響應信息,最后關閉連接。
RestTemplate是Spring中對HttpClient的再次封裝,簡化了發起HTTP請求以及處理響應的過程,抽象層級更高,減少消費者的模板代碼,使冗余代碼更少。
其實仔細想想Spring Boot下的很多XXXTemplate類,它們也提供各種模板方法,只不過抽象的層次更高,隱藏了更多細節而已。
順便提一下,Spring Cloud有一個聲明式服務調用Feign,是基於Netflix Feign實現的,整合了Spring Cloud Ribbon與 Spring Cloud Hystrix,並且實現了聲明式的Web服務客戶端定義方式。
本質上Feign是在RestTemplate的基礎上對其再次封裝,由它來幫助我們定義和實現依賴服務接口的定義。
2、RestTemplate常見方法
常見的REST服務有很多種請求方式,如GET,POST,PUT,DELETE,HEAD,OPTIONS等。RestTemplate實現了最常見的方式,用的最多的就是Get和Post了,調用API可參考源碼,這里列舉幾個方法定義(GET、POST、DELETE):
public <T> T getForObject(String url, Class<T> responseType, Object... uriVariables) public <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Object... uriVariables) public <T> T postForObject(String url, @Nullable Object request, Class<T> responseType,Object... uriVariables) public <T> ResponseEntity<T> postForEntity(String url, @Nullable Object request,Class<T> responseType, Object... uriVariables) public void delete(String url, Object... uriVariables) public void delete(URI url) methods
同時要注意兩個較為“靈活”的方法exchange和execute。
RestTemplate暴露的exchange與其它接口的不同:
(1)允許調用者指定HTTP請求的方法(GET,POST,DELETE等)
(2)可以在請求中增加body以及頭信息,其內容通過參數‘HttpEntity<?>requestEntity’描述
(3)exchange支持‘含參數的類型’(即泛型類)作為返回類型,該特性通過‘ParameterizedTypeReference<T>responseType’描述。
RestTemplate所有的GET,POST等等方法,最終調用的都是execute方法。excute方法的內部實現是將String格式的URI轉成了java.net.URI,之后調用了doExecute方法,doExecute方法的實現如下:
/** * Execute the given method on the provided URI. * <p>The {@link ClientHttpRequest} is processed using the {@link RequestCallback}; * the response with the {@link ResponseExtractor}. * @param url the fully-expanded URL to connect to * @param method the HTTP method to execute (GET, POST, etc.) * @param requestCallback object that prepares the request (can be {@code null}) * @param responseExtractor object that extracts the return value from the response (can be {@code null}) * @return an arbitrary object, as returned by the {@link ResponseExtractor} */ @Nullable protected <T> T doExecute(URI url, @Nullable HttpMethod method, @Nullable RequestCallback requestCallback, @Nullable ResponseExtractor<T> responseExtractor) throws RestClientException { Assert.notNull(url, "'url' must not be null"); Assert.notNull(method, "'method' must not be null"); ClientHttpResponse response = null; try { ClientHttpRequest request = createRequest(url, method); if (requestCallback != null) { requestCallback.doWithRequest(request); } response = request.execute(); handleResponse(url, method, response); if (responseExtractor != null) { return responseExtractor.extractData(response); } else { return 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(); } } } doExecute
doExecute方法封裝了模板方法,比如創建連接、處理請求和應答,關閉連接等。
多數人看到這里,估計都會覺得封裝一個RestClient不過如此吧?
3、簡單調用
以一個POST調用為例:
package com.power.demo.restclient; import com.power.demo.common.AppConst; import com.power.demo.restclient.clientrequest.ClientGetGoodsByGoodsIdRequest; import com.power.demo.restclient.clientresponse.ClientGetGoodsByGoodsIdResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.springframework.web.client.RestTemplate; /** * 商品REST接口客戶端 (demo測試用) **/ @Component public class GoodsServiceClient { //服務消費者調用的接口URL 形如:http://localhost:9090 @Value("${spring.power.serviceurl}") private String _serviceUrl; @Autowired private RestTemplate restTemplate; public ClientGetGoodsByGoodsIdResponse getGoodsByGoodsId(ClientGetGoodsByGoodsIdRequest request) { String svcUrl = getGoodsSvcUrl() + "/getinfobyid"; ClientGetGoodsByGoodsIdResponse response = null; try { response = restTemplate.postForObject(svcUrl, request, ClientGetGoodsByGoodsIdResponse.class); } catch (Exception e) { e.printStackTrace(); response = new ClientGetGoodsByGoodsIdResponse(); response.setCode(AppConst.FAIL); response.setMessage(e.toString()); } return response; } private String getGoodsSvcUrl() { String url = ""; if (_serviceUrl == null) { _serviceUrl = ""; } if (_serviceUrl.length() == 0) { return url; } if (_serviceUrl.substring(_serviceUrl.length() - 1, _serviceUrl.length()) == "/") { url = String.format("%sapi/v1/goods", _serviceUrl); } else { url = String.format("%s/api/v1/goods", _serviceUrl); } return url; } } GoodsServiceClient
demo里直接RestTemplate.postForObject方法調用,反序列化實體轉換這些RestTemplate內部封裝搞定。
文獻來源於: https://www.cnblogs.com/jeffwongishandsome/archive/2018/05/17/8995562.html