前言
第一次看到這個RestTemplate
的時候,還是遙遠的2017年,記得那年去廣西亞信學習,然後從一個項目中看到了RestTemplate
,那個時候也就知道怎麼用,也就有那麼個大概的印象。後來從廣西回來後,這幾年基本上也就沒有過多的寫Java代碼了,基本就是與C++和Lua爲伴;而這些年基本也就不寫代碼了,更多的是看別人寫代碼,前段時間評審團隊的代碼時,發現在調用第三方提供的服務時,都是直接使用的HttpClient調用的,你要是使用HttpClient的話,也是沒有任何問題的,關鍵是你自己封裝的工具類還漏洞百出。後來經過評審,由於我們的整個項目都是基於Spring框架的,最後還是決定直接使用RestTemplate+HttpClient的方式調用第三方的服務。
說說RestTemplate
既然要使用RestTemplate
,然後自己又好幾年沒有寫Java了,然後又手癢,就寫篇文章總結下這個RestTemplate
,以及如何使用RestTemplate
。
RestTemplate
是Spring框架提供的一個在應用中調用其他Restful服務的一個工具,它簡化了與Http服務的通信方式,統一了Restful的標準,對使用方來說,提供了非常友好的上層API接口。相較於上面說的HttpClient,RestTemplate
是一種更優雅的調用Restful服務的方式。所以說,好用纔會用。
在RestTemplate
中包含以下幾個部分:
HttpMessageConverter
:對象轉換器;將請求對象轉換爲具體的數據格式輸出,例如:Jaxb2RootElementHttpMessageConverterket
提供對xml格式的輸入輸出支持;可以參見這篇文章,通過實戰搞懂HttpMessageConverter
;ClientHttpRequestFactory
:客戶端請求工廠接口,可以通過使用ClientHttpRequestFactory
指定不同的HTTP請求方式;實現了該接口就都可以與RestTemplate
進行集成,後面會細說與不同的框架進行集成;默認是JDK的HttpURLConnection
;ResponseErrorHandler
:異常錯誤處理;可以參考這篇文章,看下如何自定義異常處理;ClientHttpRequestInterceptor
:請求攔截器
以後有機會,我們會再在源碼的層面上來對上面的幾個部分進行分析。下面來看下RestTemplate
類提供的一些主要API:
方法名 | 描述 |
---|---|
| 通過GET請求獲得響應結果 |
| 通過GET請求獲取 |
| 以HEAD請求資源返回所有響應頭信息 |
| 通過POST請求創建資源,獲得響應結果 |
| 更通用版本的請求處理方法,接受一個 |
| 最通用的執行HTTP請求的方法,上面所有方法都是基於 |
下面就開始通過實戰的代碼來熟悉一下RestTemplate
。
RestTemplate實戰
說了這麼多,最重要的還是需要落實在實戰上面。下面通過一個基於Spring Boot的測試工程來演示如何使用RestTemplate
。
getForObject
使用示例:
// 不帶請求參數
@GetMapping("/getForObject")
public String getForObject() throws JsonProcessingException {
BookConfigBean bookConfigBean = restTemplate.getForObject("http://127" +
".0.0.1:8080/test2", BookConfigBean.class);
log.info(bookConfigBean.toString());
return objectMapper.writeValueAsString(bookConfigBean);
}
// 攜帶請求參數
@GetMapping("/getForObject")
public String getForObject() throws JsonProcessingException {
Map<String, String> uriVars = new HashMap<>();
uriVars.put("name", "果凍想公衆號");
uriVars.put("author", "Jelly");
BookConfigBean bookConfigBean = restTemplate.getForObject("http://127" +
".0.0.1:8080/test2?name={name}&author={author}",
BookConfigBean.class, uriVars);
log.info(bookConfigBean.toString());
return objectMapper.writeValueAsString(bookConfigBean);
}
getForEntity
使用示例:
@GetMapping("/getForEntity")
public String getForEntity() throws JsonProcessingException {
Map<String, String> uriVars = new HashMap<>();
uriVars.put("name", "果凍想公衆號");
uriVars.put("author", "Jelly");
ResponseEntity<BookConfigBean> responseEntity = restTemplate.getForEntity("http://127" +
".0.0.1:8080/test2?name={name}&author={author}",
BookConfigBean.class, uriVars);
HttpStatus statusCode = responseEntity.getStatusCode();
log.info("statusCode:" + statusCode);
int httpCode = responseEntity.getStatusCodeValue();
log.info("statusCodeValue:" + httpCode);
HttpHeaders headers = responseEntity.getHeaders();
log.info("headers:" + headers);
BookConfigBean bookConfigBean = responseEntity.getBody();
return objectMapper.writeValueAsString(bookConfigBean);
}
postForObject
使用示例:
@GetMapping("/postForObject")
public String postForObject() throws JsonProcessingException {
HttpHeaders headers = new HttpHeaders();
headers.add("token", "xxxxxxxxxxxxx");
MultiValueMap<String, String> map= new LinkedMultiValueMap<String, String>();
map.add("name", "果凍想公衆號");
map.add("author", "Jelly");
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<MultiValueMap<String, String>>(map, headers);
BookConfigBean bookConfigBean = restTemplate.postForObject("http://127" +
".0.0.1:8080/test3", request, BookConfigBean.class);
return objectMapper.writeValueAsString(bookConfigBean);
}
postForEntity
使用示例:
@GetMapping("/postForObject")
public String postForObject() throws JsonProcessingException {
HttpHeaders headers = new HttpHeaders();
headers.add("token", "xxxxxxxxxxxxx");
MultiValueMap<String, String> map= new LinkedMultiValueMap<String, String>();
map.add("name", "果凍想公衆號");
map.add("author", "Jelly");
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<MultiValueMap<String, String>>(map, headers);
ResponseEntity<BookConfigBean> responseEntity =
this.restTemplate.postForEntity("http://127" +
".0.0.1:8080/test3", request, BookConfigBean.class);
HttpStatus statusCode = responseEntity.getStatusCode();
log.info("statusCode:" + statusCode);
int httpCode = responseEntity.getStatusCodeValue();
log.info("statusCodeValue:" + httpCode);
headers = responseEntity.getHeaders();
log.info("headers:" + headers);
BookConfigBean bookConfigBean = responseEntity.getBody();
return objectMapper.writeValueAsString(bookConfigBean);
}
exchange
使用示例:
// 第一種使用方式
@GetMapping("/exchange")
public String exchange() throws JsonProcessingException {
HttpHeaders headers = new HttpHeaders();
headers.add("token", "xxxxxxxxxxxxx");
HttpEntity<String> request =
new HttpEntity<String>(null, headers);
Map<String, String> uriVars = new HashMap<>();
uriVars.put("name", "果凍想公衆號");
uriVars.put("author", "Jelly");
ResponseEntity<BookConfigBean> responseEntity =
restTemplate.exchange("http://127.0.0.1:8080/test2?name={name" +
"}&author={author}", HttpMethod.GET, request,
BookConfigBean.class, uriVars);
return objectMapper.writeValueAsString(responseEntity.getBody());
}
// 第二種使用方式
@GetMapping("/exchange")
public String exchange() throws JsonProcessingException {
HttpHeaders headers = new HttpHeaders();
headers.add("token", "xxxxxxxxxxxxx");
MultiValueMap<String, String> map= new LinkedMultiValueMap<String, String>();
map.add("name", "果凍想公衆號");
map.add("author", "Jelly");
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<MultiValueMap<String, String>>(map, headers);
ResponseEntity<BookConfigBean> responseEntity =
restTemplate.exchange("http://127.0.0.1:8080/test3",
HttpMethod.POST, request,
BookConfigBean.class);
return objectMapper.writeValueAsString(responseEntity.getBody());
}
// 第三種使用方式
@GetMapping("/exchange")
public String exchange() throws JsonProcessingException, URISyntaxException {
HttpHeaders headers = new HttpHeaders();
headers.add("token", "xxxxxxxxxxxxx");
RequestEntity requestEntity = RequestEntity.get(new URI("http://127.0" +
".0.1:8080/test2")).headers(headers).build();
ResponseEntity<BookConfigBean> responseEntity = this.restTemplate.exchange(requestEntity, BookConfigBean.class);
BookConfigBean bookConfigBean = responseEntity.getBody();
return objectMapper.writeValueAsString(bookConfigBean);
}
// 第四種使用方式
@GetMapping("/exchange")
public String exchange() throws JsonProcessingException, URISyntaxException {
HttpHeaders headers = new HttpHeaders();
headers.add("token", "xxxxxxxxxxxxx");
MultiValueMap<String, String> map= new LinkedMultiValueMap<String, String>();
map.add("name", "果凍想公衆號");
map.add("author", "Jelly");
RequestEntity requestEntity = RequestEntity.post(new URI("http://127" +
".0.0.1:8080/test3")).headers(headers).body(map);
ResponseEntity<BookConfigBean> responseEntity = this.restTemplate.exchange(requestEntity, BookConfigBean.class);
BookConfigBean bookConfigBean = responseEntity.getBody();
return objectMapper.writeValueAsString(bookConfigBean);
}
異常處理
前面也簡單說了一下異常處理,在Spring中對於RestTemplate
提供了一個默認的異常處理,我們在自定義異常處理時建議繼承該默認繼承類,例如這樣:
public class CustomErrorHandler extends DefaultResponseErrorHandler {
private final Logger log = LoggerFactory.getLogger(CustomErrorHandler.class);
public void handleError(ClientHttpResponse response) throws IOException {
// todo
log.error("自定義異常處理");
}
}
在構建RestTemplate
實例的時候,需要設置異常處理:
@Bean
public RestTemplate restTemplate(ClientHttpRequestFactory factory) {
RestTemplate restTemplate = new RestTemplate();
restTemplate.setErrorHandler(new CustomErrorHandler());
return restTemplate;
}
RestTemplate與OkHttp集成
上面也說到RestTemplate
默認是與JDK的HttpURLConnection
進行的集成,但是在實際生產項目中,HttpURLConnection
能力不夠強大,無法滿足我們的要求。而RestTemplate本身又具備與實現了ClientHttpRequestFactory
接口的Http Client類庫進行集成,在實際工作中,最多的就是RestTemplate與OkHttp集成和RestTemplate與HttpClient集成,這裏就重點把這兩種集成方式進行總結。
至於OkHttp的牛叉之處,我這裏就不再敘述了,因爲好用,我這裏才進行總結。
要使用OkHttp,首先要添加以下Maven依賴:
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.9.0</version>
</dependency>
接下來,我們對OkHttp3進行配置:
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate(ClientHttpRequestFactory factory) {
return new RestTemplate(factory);
}
@Bean
public ClientHttpRequestFactory okHttp3clientHttpRequestFactory() {
OkHttpClient httpClient = new OkHttpClient.Builder()
//.sslSocketFactory(sslSocketFactory(), x509TrustManager())
.retryOnConnectionFailure(false)
.connectionPool(pool())
.connectTimeout(5, TimeUnit.SECONDS)
.readTimeout(5, TimeUnit.SECONDS)
.writeTimeout(5, TimeUnit.SECONDS)
.build();
return new OkHttp3ClientHttpRequestFactory(httpClient);
}
@Bean
public X509TrustManager x509TrustManager() {
return new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
}
@Override
public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
};
}
@Bean
public SSLSocketFactory sslSocketFactory() {
try {
//信任任何鏈接
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[]{x509TrustManager()}, new SecureRandom());
return sslContext.getSocketFactory();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (KeyManagementException e) {
e.printStackTrace();
}
return null;
}
@Bean
public ConnectionPool pool() {
return new ConnectionPool(200, 5, TimeUnit.MINUTES);
}
}
RestTemplate與HttpClient集成
對於HttpClient,首先要添加以下Maven依賴:
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.13</version>
</dependency>
接下來,我們對HttpClient進行配置:
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate(ClientHttpRequestFactory factory) {
return new RestTemplate(factory);
}
@Bean
public ClientHttpRequestFactory httpComponentsClientHttpRequestFactory(){
//Httpclient連接池,長連接保持30秒
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(30, TimeUnit.SECONDS);
//設置總連接數
connectionManager.setMaxTotal(1000);
//設置同路由的併發數
connectionManager.setDefaultMaxPerRoute(1000);
//設置header
List<Header> headers = new ArrayList<Header>();
headers.add(new BasicHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.3; rv:36.0) Gecko/20100101 Firefox/36.04"));
headers.add(new BasicHeader("Accept-Encoding", "gzip, deflate"));
headers.add(new BasicHeader("Accept-Language", "zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3"));
headers.add(new BasicHeader("Connection", "keep-alive"));
//創建HttpClient
HttpClient httpClient = HttpClientBuilder.create()
.setConnectionManager(connectionManager)
.setDefaultHeaders(headers)
.setRetryHandler(new DefaultHttpRequestRetryHandler(3, true)) //設置重試次數
.setKeepAliveStrategy(new DefaultConnectionKeepAliveStrategy()) //設置保持長連接
.build();
//創建HttpComponentsClientHttpRequestFactory實例
HttpComponentsClientHttpRequestFactory requestFactory =
new HttpComponentsClientHttpRequestFactory(httpClient);
//設置客戶端和服務端建立連接的超時時間
requestFactory.setConnectTimeout(5000);
//設置客戶端從服務端讀取數據的超時時間
requestFactory.setReadTimeout(5000);
//設置從連接池獲取連接的超時時間,不宜過長
requestFactory.setConnectionRequestTimeout(200);
//緩衝請求數據,默認爲true。通過POST或者PUT大量發送數據時,建議將此更改爲false,以免耗盡內存
requestFactory.setBufferRequestBody(false);
return requestFactory;
}
}
總結
各種找資料,編碼驗證,用了好久,終於搞定這篇文章,這篇文章裏面最大的就是用代碼說話,文中的所有代碼都經過測試,可以直接複製使用。總結了這篇文章,以後在使用RestTemplate
的時候會更加從容和得心應手。
2020年11月7日 於呼和浩特。
人生是個圓,有的人走了一輩子也沒有走出命運畫出的圓圈,其實,圓上的每一個點都有一條騰飛的切線。
玩代碼、玩技術
長按識別二維碼,關注“果凍想”
如果覺得還不錯,可以點個“在看”哦~