@TOC
目的是在需要的時候以最快的、無bug、可承載大併發大資料下的標準使用。
可供日常學習、編寫指令碼、開發時使用。
希望閱讀本文後,不會再被任何http、介面請求類的問題嚇到!有問題找blog,解決不了請留言,會及時補充,永久更新!。
整體架構圖
基本使用與原理
RestTemplate主要方法
http方法 | RestTemplate方法 |
---|---|
GET | getForObject(String url, Class responseType,Object… urlVariables) |
POST | postForObject(String url, Object request,Class responseType,Object… urlVariables) |
PUT | put(String url, Object request,Object… urlVariables) |
DELETE | delete(String url,Object… urlVariables) |
HEAD | headForHeaders(String url,Map<String,?> urlVariables) |
OPTIONS | optionsForAllow(String url,Object… urlVariables) |
推薦這些方法我們都不記,只記錄一個:exchange |
URI與引數
URL
- 建立URL使用Uri Builder類,常見的方法有:
public static UriComponentsBuilder newInstance();
public static UriComponentsBuilder fromUri(URI uri);
public static UriComponentsBuilder fromHttpRequest(HttpRequest request);
public static UriComponentsBuilder fromUriString(String uri);
其中最常用的為fromUriString,本文以此方法為主。
原理如截圖,透過給定的url,使用正規表示式,分塊匹配url字串中的每一部分,得到scheme,host,port…等資訊,最後build到物件屬性中。
示例:
// 使用newInstance方法構建,不推薦
UriComponents uriComponents = UriComponentsBuilder.newInstance()
.scheme("http")
.host("www.test2.com")
.path("/test")
.queryParam("query", "a")
.build();
// 使用fromUriString,強烈推薦
String url = UriComponentsBuilder.fromUriString("http://www.baidu.com/test?query1=1").build();
- UriComponentsBuilder常用方法:
方法 | 介紹 |
---|---|
queryParam | 新增get引數,如果存在則替換,否則新增。例子:queryParam(“query2”, 2) |
replaceQueryParam | 替換get引數,與queryParam類似,為了增強語意 |
queryParams | 批次增加get引數,入參是值 一個Key對應多個Value的MultiValueMap |
replaceQueryParams | 先清空引數列表,再批次增加get引數,入參是MultiValueMap |
expand | 替換所有url佔位符 |
encode | 對url字串進行編碼,預設是utf-8,編碼格式可以在引數中指定 |
toUriString | 輸出url最終字串,會自動呼叫encode進行編碼 |
toString | 輸出url原始字串,不會自動呼叫encode進行編碼 |
對於編碼此處特殊程式碼說明: |
/**
* 情況一
*/
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString("http://www.test2.com/test?query=哈哈");
// 可以編碼成功
String s = builder.toUriString();
/**
* 情況二
*/
UriComponents uriComponents = UriComponentsBuilder.newInstance()
.scheme("http")
.host("www.test2.com")
.path("/test")
.queryParam("query", "a")
.build();
// 編碼失敗
String s = uriComponents.toUriString();
// 正確的方式,需要使用builder類的toUriString(),使用UriComponents類的toUriString不起作用,可以用encode手動實現
String s = uriComponents.encode().toString();
// 總結,沒事別用newInstance構造,老老實實使用fromUriString
- UriComponents類
可以理解為是一個Uri的構造體,可以透過此類拿到想要的uri部件中的各種資訊,如host、queryparam等。可以透過UriComponentsBuilder.build方法獲取UriComponents。並透過UriComponents.encode.toString()方法對url進行編碼,效果為urlenconde。常用的為簡化版:
UriComponents.toUriString();
常用方法:
public void test01(){
UriComponentsBuilder u = UriComponentsBuilder.fromUriString("http://www.test.com/{grd}/test?query1=1");
UriComponents uriComponents = u.build().expand("heihei");
String s = uriComponents.toUriString();
String query = uriComponents.getQuery();
MultiValueMap<String, String> queryParams = uriComponents.getQueryParams();
String getPath = uriComponents.getPath();
List<String> getPathSegments = uriComponents.getPathSegments();
String getUserInfo = uriComponents.getUserInfo();
String getFragment = uriComponents.getFragment();
System.out.println(s);
System.out.println("query:"+query);
System.out.println("queryParams"+queryParams);
System.out.println("getPath:"+getPath);
System.out.println("getPathSegments:"+getPathSegments);
}
// 輸出結果
http://www.test.com/heihei/test?query1=1
query:query1=1
queryParams{query1=[1]}
getPath:/heihei/test
getPathSegments:[heihei, test]
引數
每一個方法,第一個引數都是uri,uri除了手寫,都可以透過URL類去構建。
引數可以用陣列傳遞進去(陣列指的是動態引數),也可以用Map代替。
// 動態陣列
String result = restTemplate.getForObject("http://aaa.com/test/{param1}/tt/{param2}", String.class, "11", "22");
// Map
Map<String, String> vars = new HashMap<String, String>();
vars.put("param1", "11");
vars.put("param2", "22");
String result = restTemplate.getForObject("http://aaa.com/test/{param1}/tt/{param2", String.class, vars);
HttpEntity與RequestEntity
HttpEntity
HttpEntity對header與body進行了封裝,它的結構體非常簡單:
public class HttpEntity<T> {
private final HttpHeaders headers;
private final T body;
}
其中HttpHeaders是一個MultiValueMap<String, String>,總結如下:
- 幾乎所有的header都封裝了常量,所以以後設定header不用再傻傻的拼寫了,直接在此類搜尋。如HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS等
- 注意字符集編碼不要傻傻的寫UTF_8了,用常量StandardCharsets
- 此類還提供了一個BasicAuth的小實現:HttpHeaders.encodeBasicAuth(“gurundong”, “123456”, StandardCharsets.UTF_8);
- HttpEntity.EMPTY,建立一個空的HttpEntity,主要用於在exchange方法中使用,簡寫。
- 示例程式碼:
public void test01(){
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add(HttpHeaders.ACCEPT,"13");
httpHeaders.add(HttpHeaders.ACCEPT_CHARSET,"24");
System.out.println(httpHeaders.toString());
}
RequestEntity
RequestEntity extends HttpEntity,又新封裝了HttpMethod與URI屬性。實際配合exchange使用時,個人感覺直接用HttpEntity更好,程式碼更直觀。
ClientHttpRequestFactory
RestTemplate整合了HttpAccessor基礎抽象類,在HttpAccessor中,定義了
requestFactory預設實現。也可以透過RestTemplate(ClientHttpRequestFactory requestFactory)的建構函式,傳入自定義的ClientHttpRestFactory。
HttpUrlConnection實現
- SimpleClientHttpRequestFactory的實現,底層為JDK自帶的HttpURLConnection,每執行一次exchange方法,都會新開一個tcp連結控制程式碼。 請求 < = > tcp連結一一對應。linux預設的tcp連結控制程式碼限制是1024(雖然可以透過配置調大),這會極大的限制併發,不能夠複用tcp通道,浪費大量的tcp連結。
private ClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
HttpClinet實現
除了JDK自帶HttpURLConnection實現的SimpleClientHttpRequestFactory,其它常用的還有HttpComponentsClientHttpRequestFactory、OkHttp3ClientHttpRequestFactory等。我們只看由HttpClient實現的HttpComponentsClientHttpRequestFactory。
- HttpComponentsClientHttpRequestFactory有幾種構造方法,其中最常用的有無參構造方法、以及有參(HttpClient httpClient)構造方法。在生產環境,一定不可使用無參構方法,因為不配置會使用配置配置,由httpClient構建的預設連線池會非常小(預設最大連線是20,每個路由最大連線是2)。
- 此處簡單小補一下HttpClient的連線池原理圖:
連線池配置
- 附上http自定義HttpClient連線池的程式碼,以後方便直接使用
@Configuration
public class RestTemplateConfig {
Logger logger = LoggerFactory.getLogger(RestTemplateConfig.class);
@Bean
public RestTemplate restTemplate() {
return new RestTemplate(clientHttpRequestFactory());
}
@Bean
public ClientHttpRequestFactory clientHttpRequestFactory() {
/**
* 連結配置
*/
RequestConfig.Builder configBuilder = RequestConfig.custom();
// 設定連線超時,連結到目標介面 30秒
configBuilder.setConnectTimeout(30000);
// 設定讀取超時,等待目標介面超時 60秒
configBuilder.setSocketTimeout(60000);
// 設定從連線池獲取連線例項的超時,等待連線池可用連結超時 10秒
configBuilder.setConnectionRequestTimeout(10000);
// 在提交請求之前 測試連線是否可用
// configBuilder.setStaleConnectionCheckEnabled(true);
//cookie管理規範設定,此處有多種可以設定,按需要設定
// configBuilder.setCookieSpec(CookieSpecs.BROWSER_COMPATIBILITY);
RequestConfig requestConfig = configBuilder.build();
/**
* 池化定義
*/
// 預設該實現會為每個路由保持2個並行連線,總的數量上不超過20個連線
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
// 連線池的最大連線數
cm.setMaxTotal(200);
// 每個路由的最大連線數
cm.setDefaultMaxPerRoute(20);
// 某個路由的最大連線數
HttpHost localhost = new HttpHost("www.baidu.com", 80);
cm.setMaxPerRoute(new HttpRoute(localhost), 50);
/**
* 模擬瀏覽器cookie,設定到全域性httpClient上,每次都傳送都可以攜帶
*/
CookieStore cookieStore = new BasicCookieStore();
BasicClientCookie cookie = new BasicClientCookie("name", "value");
cookie.setDomain("www.grd.com");
cookie.setPath("/");
cookieStore.addCookie(cookie);
/**
* 配置定義
*/
// 建立可服用的httpClient
// CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(cm).build();
// httpClientBuilder配置構架器
HttpClientBuilder httpClientBuilder = HttpClients.custom();
// 設定預設請求配置
httpClientBuilder.setDefaultRequestConfig(requestConfig);
// 設定重試次數,此處注意,如果使用無參構造,重試次數為3。this(3, false);
httpClientBuilder.setRetryHandler(new DefaultHttpRequestRetryHandler(2, false));
// 設定預設cookie配置
// httpClientBuilder.setDefaultCookieStore(cookieStore);
// 設定預設https配置
// httpClientBuilder.setSSLSocketFactory(createSSLConn());
// httpClientBuilder.setDefaultConnectionConfig()
// 設定預設header配置
// httpClientBuilder.setDefaultHeaders();
// 獲取httpClient
CloseableHttpClient httpClient = httpClientBuilder.build();
// 配置HttpClient的對應工廠HttpComponentsClientHttpRequestFactory
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient);
factory.setConnectTimeout(10000); // 連結超時
factory.setReadTimeout(10000);
factory.setConnectionRequestTimeout(1000);
return factory;
}
@Bean
private static SSLConnectionSocketFactory createSSLConn() {
SSLConnectionSocketFactory sslsf = null;
try
{
SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
return true;
}
}).build();
sslsf = new SSLConnectionSocketFactory(sslContext);
} catch (GeneralSecurityException e)
{
e.printStackTrace();
}
return sslsf;
}
/**
* 自定義的重試策略,需要的時候使用
*/
public void customHttpRequestRetryHandler(){
//請求失敗時,進行請求重試
HttpRequestRetryHandler handler = new HttpRequestRetryHandler() {
@Override
public boolean retryRequest(IOException e, int i, HttpContext httpContext) {
if (i > 3){
//重試超過3次,放棄請求
logger.error("retry has more than 3 time, give up request");
return false;
}
if (e instanceof NoHttpResponseException){
//伺服器沒有響應,可能是伺服器斷開了連線,應該重試
logger.error("receive no response from server, retry");
return true;
}
if (e instanceof SSLHandshakeException){
// SSL握手異常
logger.error("SSL hand shake exception");
return false;
}
if (e instanceof InterruptedIOException){
//超時
logger.error("InterruptedIOException");
return false;
}
if (e instanceof UnknownHostException){
// 伺服器不可達
logger.error("server host unknown");
return false;
}
if (e instanceof ConnectTimeoutException){
// 連線超時
logger.error("Connection Time out");
return false;
}
if (e instanceof SSLException){
logger.error("SSLException");
return false;
}
HttpClientContext context = HttpClientContext.adapt(httpContext);
HttpRequest request = context.getRequest();
if (!(request instanceof HttpEntityEnclosingRequest)){
//如果請求不是關閉連線的請求
return true;
}
return false;
}
};
}
}
如何使用HttpClient的全套功能
很簡單,可以直接透過HttpComponentsClientHttpRequestFactory拿到HttpClient!直接繞過RestTemplate,享用原生HttpClient的所有功能。
HttpComponentsClientHttpRequestFactory requestFactory = (HttpComponentsClientHttpRequestFactory) restTemplate.getRequestFactory();
HttpClient httpClient = requestFactory.getHttpClient();
ResponseEntity
- ResponseEntity繼承與HttpEntity,新增了status成員變數。使用ResponseEntity作為controller的返回值,我們可以方便地處理響應的header,狀態碼以及body。而通常使用的@ResponseBody註解,只能處理body部分。這也是為什麼通常在下載場景中會使用ResponseEntity,因為下載需要設定header裡的content-type以及特殊的status(比如206)。
- ResponseBody可以直接返回Json結果,ResponseEntity不僅可以返回json結果,還可以定義返回的HttpHeaders和HttpStatus。
- 溫習一下ResponseEntity在Spring mvc中的實現:HttpEntityMethodProcessor的handleReturnValue方法
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
// 獲取ServletReuqest 與 ServletResponse
ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
Assert.isInstanceOf(HttpEntity.class, returnValue);
// 獲取到responseEntity
HttpEntity<?> responseEntity = (HttpEntity<?>) returnValue;
// 將responseEntity的Header資訊加入到ServletResponse
HttpHeaders outputHeaders = outputMessage.getHeaders();
HttpHeaders entityHeaders = responseEntity.getHeaders();
if (!entityHeaders.isEmpty()) {
entityHeaders.forEach((key, value) -> {
if (HttpHeaders.VARY.equals(key) && outputHeaders.containsKey(HttpHeaders.VARY)) {
List<String> values = getVaryRequestHeadersToAdd(outputHeaders, entityHeaders);
if (!values.isEmpty()) {
outputHeaders.setVary(values);
}
}
else {
outputHeaders.put(key, value);
}
});
}
// 如果是ResponseEntity,直接flush,返回資料。
if (responseEntity instanceof ResponseEntity) {
int returnStatus = ((ResponseEntity<?>) responseEntity).getStatusCodeValue();
outputMessage.getServletResponse().setStatus(returnStatus);
if (returnStatus == 200) {
if (SAFE_METHODS.contains(inputMessage.getMethod())
&& isResourceNotModified(inputMessage, outputMessage)) {
// Ensure headers are flushed, no body should be written.
outputMessage.flush();
// Skip call to converters, as they may update the body.
return;
}
}
else if (returnStatus / 100 == 3) {
String location = outputHeaders.getFirst("location");
if (location != null) {
saveFlashAttributes(mavContainer, webRequest, location);
}
}
}
// 轉換body
writeWithMessageConverters(responseEntity.getBody(), returnType, inputMessage, outputMessage);
// Ensure headers are flushed even if no body was written.
outputMessage.flush();
- ResponseEntity在RestTemplate中的應用,非常簡單,ResponseEntity只是用來做返回值的載體,封裝了header、httpStatus、body資訊,方便熟悉Spring mvc的人使用。原理程式碼如下:
private class ResponseEntityResponseExtractor<T> implements ResponseExtractor<ResponseEntity<T>> {
@Override
public ResponseEntity<T> extractData(ClientHttpResponse response) throws IOException {
// 此步驟將ClientHttpResponse中的InputStream型別的body,轉化為T型別的Body。
T body = this.delegate.extractData(response);
// 將statusCode、HttpHeader、Body封裝入ResponseEntity以供使用。
return ResponseEntity.status(response.getRawStatusCode()).headers(response.getHeaders()).body(body);
}
}
ParameterizedTypeReference
RestTemplate異常整理
在請求傳送時,返回不是200都會拋異常。
注意HttpStatusCodeException、HttpClientErrorException、HttpServerErrorException,主要使用這三個。
RestTemplate最關鍵的exchange方法(不想學原理只想使用的直接看這裡)
使用demo
- http
public class Test {
public static void main(String[] args) {
// RestTemplate template = new RestTemplate();
// ResponseEntity<String> exchange = template.exchange("https://blog.csdn.net/qq_37099837/article/details/112461675", HttpMethod.GET, HttpEntity.EMPTY, String.class);
// System.out.println(exchange.getStatusCodeValue());
RestTemplate restTemplate = new RestTemplate();
// 加列印url與header的interceptor
restTemplate.setInterceptors(Collections.singletonList(printTemplateInterceptor()));
// 設定header
MultiValueMap<String,String> header = new LinkedMultiValueMap<>();
// 設定cookie
String cookies = "xxwww:xxxsss;";
header.add(HttpHeaders.COOKIE,cookies);
HttpEntity<MultiValueMap<String,String>> httpEntity = new HttpEntity<>(header);
// 設定路徑引數
MultiValueMap<String,String> vars = new LinkedMultiValueMap<>();
vars.add("param1","grd");
// url
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString("http://www.test2.com/{param1}/test");
// 新增get引數
builder.queryParam("query","dfadsf");
ResponseEntity<String> responseEntity = restTemplate.exchange(builder.toUriString(), HttpMethod.GET, httpEntity, String.class,vars);
String res = responseEntity.getBody();
System.out.println(res);
}
public static ClientHttpRequestInterceptor printTemplateInterceptor(){
return new ClientHttpRequestInterceptor(){
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
// 透過請求獲取請求中的header引數
HttpHeaders headers = request.getHeaders();
System.out.println("url:"+request.getURI().toString());
System.out.println("headers:"+headers);
return execution.execute(request, body);
}
};
}
}
- https
此處只講針對HttpClient實現的配置:
HttpClientBuilder httpClientBuilder = HttpClients.custom();
httpClientBuilder.setSSLSocketFactory(createSSLConn());
// 獲取httpClient
CloseableHttpClient build = httpClientBuilder.build();
// 配置HttpClient的對應工廠HttpComponentsClientHttpRequestFactory
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(build);
new RestTemplate(factory)
// Https配置
private static SSLConnectionSocketFactory createSSLConn() {
SSLConnectionSocketFactory sslsf = null;
try
{
SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
return true;
}
}).build();
sslsf = new SSLConnectionSocketFactory(sslContext);
} catch (GeneralSecurityException e)
{
e.printStackTrace();
}
return sslsf;
}
- 連線池配置:
直接看ClientHttpRequestFactory 章節原理
exchange方法只是做了簡單的封裝,最終呼叫doExecute。
一般就用這個方法,提供url、HttpMethod、HttpEntity、responseType即可
最關鍵的doExecute解析。主要分為4個步驟:public <T> ResponseEntity<T> exchange(String url, HttpMethod method,HttpEntity<?> requestEntity, Class<T> responseType, Object... uriVariables) throws RestClientException { // 將HttpEntity、responseType封裝到統一的HttpEntityRequestCallback中 RequestCallback requestCallback = httpEntityCallback(requestEntity, responseType); // 將responseType封裝到ResponseEntityResponseExtractor中 ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType); return nonNull(execute(url, method, requestCallback, responseExtractor, uriVariables)); }
建立ClientHttpRequest物件->Java Class轉化為請求body輸出流->執行請求->錯誤處理->響應body轉化為Java Class
分步解析:Java Class 與 body 的互轉通用程式碼protected <T> T doExecute(URI url, @Nullable HttpMethod method, @Nullable RequestCallback requestCallback, @Nullable ResponseExtractor<T> responseExtractor) throws RestClientException { ...省略 try { // 建立ClientHttpRequest物件方法,實際會呼叫getRequestFactory().createRequest(url, method) ClientHttpRequest request = createRequest(url, method); if (requestCallback != null) { // 進行實際請求前對ClientHttpRequest物件的繼續賦值補充,裡面最關鍵的一步是透過遍歷可用的HttpMessageConverter,透過請求body體的Class型別+http的content-type requestBodyClass來進行java類到body OutputStream的轉換 requestCallback.doWithRequest(request); } // 實際執行請求,返回ClientHttpResponse response = request.execute(); // 通用錯誤處理,預設使用DefaultResponseErrorHandler handleResponse(url, method, response); // 響應中body體到java Class的轉換,最後返回java Class,流程與上面的doWithRequest類似 return (responseExtractor != null ? responseExtractor.extractData(response) : null); } catch (IOException ex) { ... } }
// 遍歷可用的HttpMessageConverter
...
// 透過Java Class與content-type判斷是否執行此HttpMessageConverter轉換
if(messageConverter.canWrite(requestBodyClass, requestContentType)) {
//執行轉換,將轉換後的結果放入httpRequest的body中
((HttpMessageConverter<Object>) messageConverter).write(requestBody, requestContentType,httpRequest);
}
其中,HttpMessageConverter在RestTemplate初始化的時候就已經載入了,舉例載入jackson的原理:
```java
private static final boolean jackson2Present =
ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper",
RestTemplate.class.getClassLoader()) &&
ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator",
RestTemplate.class.getClassLoader());
public RestTemplate() {
...
if (jackson2XmlPresent) {
this.messageConverters.add(new MappingJackson2XmlHttpMessageConverter());
}
...
}
錯誤處理程式碼:
```java
ResponseErrorHandler errorHandler = getErrorHandler();
// 判斷是否有錯
boolean hasError = errorHandler.hasError(response);
// 有錯呼叫handleError,預設DefaultResponseErrorHandler實現是拋異常。
if (hasError) {
errorHandler.handleError(url, method, response);
}
RestTemplate責任鏈模式ClientHttpRequestInterceptor
原理很簡單,RestTemplate繼承了InterceptingHttpAccessor,RestTemplate extends InterceptingHttpAccessor implements HttpAccessor,其中getRequestFactory()方法被重寫了:
// 如果interceptors不為空,則使用InterceptingClientHttpRequestFactory工廠。
if (!CollectionUtils.isEmpty(interceptors)) {
ClientHttpRequestFactory factory = this.interceptingRequestFactory;
if (factory == null) {
factory = new InterceptingClientHttpRequestFactory(super.getRequestFactory(), interceptors);
this.interceptingRequestFactory = factory;
}
return factory;
}
InterceptingClientHttpRequestFactory工廠使用InterceptingRequestExecution執行execute方法,就是重寫了上面我們提到的透過ClientHttpRequest.execute()獲取ClientHttpResponse的過程。
在InterceptingClientHttpRequestFactory的execute方法中,先執行完所有的interceptors,其中,interceptors的結構是List,不管使用arrayList或者LinkedList,都是有序的,按順序執行完所有的攔截器,最後執行實際請求程式碼。
上簡單易懂的原始碼:
@Override
public ClientHttpResponse execute(HttpRequest request, byte[] body) throws IOException {
// 如果存在攔截器,則先執行完攔截器,每一次執行都可以更改Header、uri、以及body等資訊。
if (this.iterator.hasNext()) {
ClientHttpRequestInterceptor nextInterceptor = this.iterator.next();
// 此處比較有意思,執行的是類似與dfs的遞迴呼叫
return nextInterceptor.intercept(request, body, this);
}
// 當dfs條件不滿足,執行完攔截器後,進行實際的傳送請求,得到最終的ClientHttpResponse
else {
.......
return delegate.execute();
}
}
如果此攔截器不是最後一個,想繼續呼叫其它攔截器,則最後使用return execution.execute(request, body);繼續遞迴呼叫其下一個攔截器。如果不想再遞迴了,作為最後一個攔截器,可以使用自行實現的方法execute(如ribben),直接return ClientHttpResponse即可。
為方便理解,再來一段簡單的程式碼,自動加header實現微服務間的呼叫:
public ClientHttpRequestInterceptor restAuthorizationTemplateInterceptor(){
return new ClientHttpRequestInterceptor(){
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
// 透過請求獲取請求中的header引數
HttpHeaders headers = request.getHeaders();
// 往header中設定自己需要的引數
headers.add("Authorization", getAuthorization());
headers.add("Content-Type", "application/json");
headers.add("Accept", MediaType.APPLICATION_JSON.toString());
return execution.execute(request, body);
}
};
}
@Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>();
// 攔截器按照新增的順序執行
interceptors.add(restAuthorizationTemplateInterceptor());
restTemplate.setInterceptors(interceptors);
return restTemplate;
}
RestTemple與Springcloud
@LoadBalanced實現請求負載均衡
透過攔截器在RestTemplate工作前進行負載均衡。該攔截器中注入了LoadBalancerClient(負載均衡客戶端)的實現。
// 使用了一個LoadBalancerInterceptor攔截器,實現對uri的重寫
public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {
//Ribbon客戶端
private LoadBalancerClient loadBalancer;
...
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) throws IOException {
....
//使用Ribbon執行呼叫
return (ClientHttpResponse)this.loadBalancer.execute(serviceName, this.requestFactory.createRequest(request, body, execution));
}
}
透過上面的程式碼我們看出請求最終交給LoadBalanceClient例項去執行,LoadBalanceClient是Ribbon提供的負載均衡客戶端,很多重要的邏輯都在這裡處理。如下為LoadBalanceClient的類圖:
RestTemple與其它框架(如HttpClient)是如何整合的?
詳細見本文的ClientHttpRequestFactory章節
如何進行併發請求?
內容過多,原理單獨篇章實現吧。
僅做以下幾點總結,不展示程式碼了,大家都會:
- 多執行緒實現的併發,在主執行緒維度看,屬於非同步阻塞,在工作執行緒維度看,屬於同步阻塞。併發量一般,做一般的業務夠了。
- 在底層看,實現非阻塞必須使用epoll,或者select、pool實現。
非同步可以使用開執行緒、協程實現。
頂級模型為協程模型,自動實現epoll事件排程(非阻塞)+開協程(非同步)。
java實現為基於netty的Reactors 多執行緒模型,使用執行緒實現非同步,epool實現非阻塞。效率可與協程媲美。 - 同步阻塞:普通呼叫屬於同步阻塞,開多執行緒處理在工作執行緒維度看依舊屬於同步阻塞。
同步非阻塞、
非同步阻塞:開多執行緒處理,在主執行緒維度看,屬於非同步阻塞。
非同步非阻塞、 - 超大併發,想借力netty,可使用WebClient
當請求書併發過高,產生的連線池不夠問題。
檢視ClientHttpRequestFactory的連線池配置章節,對連線數不夠產生的原因與解決方案做了解釋。
本作品採用《CC 協議》,轉載必須註明作者和本文連結