平常工做中確定會遇到服務之間的調用,尤爲是如今都是微服務的架構,因此總結一下restTemplate的最經常使用的用法以及本身踩過的坑。html
restTemplate底層調用的是Execute方法,而Execute底層調用的是doExecute,它是基於http協議的,底層仍是httpClient 的使用。java
/**
* 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, "URI is required");
Assert.notNull(method, "HttpMethod is required");
ClientHttpResponse response = null;
try {
ClientHttpRequest request = createRequest(url, method);
if (requestCallback != null) {
requestCallback.doWithRequest(request);
}
response = request.execute();
handleResponse(url, method, response);
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();
}
}
}
複製代碼
咱們通常都是用的restTepmlate的exchange方法,這個方法比較靈活,能夠接受可變參數,重載方法也有不少。 固然 restTemplate還有其餘不少方法,並且遵循restFul風格,像PUT POST GET PATCH DELETE 等都有對應的方法,按需使用。這裏就不貼源碼了。git
而後就貼一個使用案例代碼上來:github
public YourResponse sampleRestTepmlate (YourRequest request) throws Exception {
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(this.serviceUrl);
builder.path("urlpath");
log.info("url : {}, request : {}", builder.toUriString(), JsonUtils.toJson(request));
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.set("headername","headervalue");
headers.add("anotherway", "value");
HttpEntity<YourRequest> requestEntity = new HttpEntity<>(request, headers);
ResponseEntity<YourResponse> responseEntity = null;
try {
responseEntity = restTemplate.exchange(builder.toUriString(), HttpMethod.POST, requestEntity,
YourResponse.class);
return responseEntity.getBody();
} catch (Exception e) {
log.error("exception:{}",e.getMessage());
}
}
複製代碼
這裏就要說一下我遇到的坑了。 在使用restTemplate的時候,當你的call沒有成功返回200的時候,好比返回400 500之類的,restTemplate裏面有一個DefaultResponseErrorHandler,他會自動攔截住這些httpstatus 爲400 500的response而後給你拋出一個異常。這就意味着,當你也想拿到帶有錯誤信息的response的時候,他不會給你!它會給你拋出exception而且只是給你返回一個簡單的相似500 Internal error! WTF!spring
貼上這段坑爹的代碼:bash
/**
* Handle the error in the given response with the given resolved status code.
* <p>This default implementation throws a {@link HttpClientErrorException} if the response status code
* is {@link org.springframework.http.HttpStatus.Series#CLIENT_ERROR}, a {@link HttpServerErrorException}
* if it is {@link org.springframework.http.HttpStatus.Series#SERVER_ERROR},
* and a {@link RestClientException} in other cases.
* @since 5.0
*/
protected void handleError(ClientHttpResponse response, HttpStatus statusCode) throws IOException {
switch (statusCode.series()) {
case CLIENT_ERROR:
throw new HttpClientErrorException(statusCode, response.getStatusText(),
response.getHeaders(), getResponseBody(response), getCharset(response));
case SERVER_ERROR:
throw new HttpServerErrorException(statusCode, response.getStatusText(),
response.getHeaders(), getResponseBody(response), getCharset(response));
default:
throw new UnknownHttpStatusCodeException(statusCode.value(), response.getStatusText(),
response.getHeaders(), getResponseBody(response), getCharset(response));
}
}
複製代碼
遇到了坑就不要懼怕,這個問題能夠這麼解決:架構
1.不用restTemplate去請求,能夠採用httpClient底層去實現socket
2.重寫handleError方法,自定義ErrorHandle繼承DefaultResponseErrorHandleride
在已經寫完實現以後,我選擇方式2 : )微服務
@Builder
@Slf4j
public class MyErrorHandle extends DefaultResponseErrorHandler {
@Override
public void handleError(ClientHttpResponse response, HttpStatus statusCode) throws IOException {
int status = statusCode.value();
if (status == 200 || status == 400 || status == 500) {
//do what u want to do
} else {
super.handleError(response,statusCode);
}
}
}
複製代碼
而後在初始化restTemplate的時候調用setErrorHandle方法就能夠了。
restTemplate.setErrorHandler(YourErrorHandle).
複製代碼
至於方式一這裏不提了。
有的時候當咱們調用對方的server時,基於https 的協議是須要導入證書的,那咱們該怎麼把證書融入到restTemplate中呢?(又一個坑)
@Bean
public RestTemplate buildRestTemplateWithinSSl(@Value("${service.connectTimeout}") int connectTimeout,
@Value("${service.readTimeout}") int readTimeout,
@Value("${service.sslFilePath}") String filePath,
@Value("${service.sslpassword}") String sslPassword) throws Exception{
RestTemplate template = restTemplateBuilder.setConnectTimeout(connectTimeout).setReadTimeout(readTimeout).build();
String workingDirectory = BeanUtility.getWorkingDirectory();
SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(new File(workingDirectory + "/" + filePath), sslPassword.toCharArray()).build();
SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE);
CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(socketFactory).build();
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient);
template.setRequestFactory(factory);
return template;
}
複製代碼
至關於從新給RequestFactory值,構造一個已經帶有ssl證書的factory給他。
這裏注意兩個地方:
SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE);
複製代碼
這裏有個參數是NoopHostnameVerifier.INSTANCE, 這裏是能夠無視ip的,也就是ip或者域名形式均可以。 (適用於對方給我提供證書和 ip地址,試了半天死活不通的狀況。。)
第二個就是一個工具類的使用,我相信不少時候new file的時候很容易被路徑繞暈。
String workingDirectory = BeanUtility.getWorkingDirectory();
複製代碼
這個工具類得到的路徑不用你去擔憂,只要你的jks文件和你的jar包同級就行。管他什麼環境什麼路徑,很方便。
貼上地址: github.com/AnsonCong/A…
本地調試證書導入jdk就行。
記錄下導入證書的方法:
keytool -import -alias {別名} -file {路徑\證書名}.cer -keystore "{jdk路徑\jre\lib\security\cacerts}" -storepass {password} -trustcacerts
複製代碼
刪除證書:
keytool -delete -alias {別名} -keystore "C:\Program Files\Java\jdk1.7.0_25\jre\lib\security\cacerts" -storepass {password}
複製代碼
查看全部安裝證書列表
keytool -list -v -keystore "C:\Users\1580977\Downloads\jdk1.8.0_101\jre\lib\security\cacerts" -storepass {password} >> C:\Desktop\abcd.txt
複製代碼
生成jks文件 (沒有默認生存,有就導入)
keytool -import -alias {別名} -file {證書名}.cer -keystore {命名}.jks
複製代碼
RestTemplate是Spring提供的用於訪問Rest服務的客戶端,RestTemplate提供了多種便捷訪問遠程Http服務的方法,可以大大提升客戶端的編寫效率。
更多restTemplate詳細資料,能夠參考: juejin.im/post/5b88b1… www.zifangsky.cn/1221.html
或者其餘掘金好文。