为什么 RestTemplate 那么棒,看这篇就够了!
作者:duanxz
來源:cnblogs.com/duanxz/p/3510622.html
在微服務都是以HTTP接口的形式暴露自身服務的,因此在調用遠程服務時就必須使用HTTP客戶端。我們可以使用JDK原生的URLConnection、Apache的Http Client、Netty的異步HTTP Client, Spring的RestTemplate。但是,用起來最方便、最優雅的還是要屬Feign了。這里介紹的是RestTemplate。
什么是RestTemplate?
RestTemplate是Spring提供的用于訪問Rest服務的客戶端,RestTemplate提供了多種便捷訪問遠程Http服務的方法,能夠大大提高客戶端的編寫效率。
調用RestTemplate的默認構造函數,RestTemplate對象在底層通過使用java.net包下的實現創建HTTP 請求,可以通過使用ClientHttpRequestFactory指定不同的HTTP請求方式。
ClientHttpRequestFactory接口主要提供了兩種實現方式
一種是SimpleClientHttpRequestFactory,使用J2SE提供的方式(既java.net包提供的方式)創建底層的Http請求連接。
一種方式是使用HttpComponentsClientHttpRequestFactory方式,底層使用HttpClient訪問遠程的Http服務,使用HttpClient可以配置連接池和證書等信息。
RestTemplate的核心之一 Http Client。
目前通過RestTemplate 的源碼可知,RestTemplate 可支持多種 Http Client的http的訪問,如下所示:
-
基于 JDK HttpURLConnection 的 SimpleClientHttpRequestFactory,默認。
-
基于 Apache HttpComponents Client 的 HttpComponentsClientHttpRequestFactory
-
基于 OkHttp3的OkHttpClientHttpRequestFactory。
-
基于 Netty4 的 Netty4ClientHttpRequestFactory。
其中HttpURLConnection 和 HttpClient 為原生的網絡訪問類,OkHttp3采用了 OkHttp3的框架,Netty4 采用了Netty框架。
?
xml配置的方式
請查看RestTemplate源碼了解細節,知其然知其所以然!
RestTemplate默認是使用SimpleClientHttpRequestFactory,內部是調用jdk的HttpConnection,默認超時為-1
@Autowired RestTemplate?simpleRestTemplate; @Autowired RestTemplate?restTemplate;基于jdk的spring的RestTemplate
<?xml?version="1.0"?encoding="UTF-8"?> <beans?xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beans?http://www.springframework.org/schema/beans/spring-beans.xsd"default-autowire="byName"?default-lazy-init="true"><!--方式一、使用jdk的實現--><bean?id="ky.requestFactory"?class="org.springframework.http.client.SimpleClientHttpRequestFactory"><property?name="readTimeout"?value="10000"/><property?name="connectTimeout"?value="5000"/></bean><bean?id="simpleRestTemplate"?class="org.springframework.web.client.RestTemplate"><constructor-arg?ref="ky.requestFactory"/><property?name="messageConverters"><list><bean?class="org.springframework.http.converter.FormHttpMessageConverter"/><bean?class="org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter"/><bean?class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/><bean?class="org.springframework.http.converter.StringHttpMessageConverter"><property?name="supportedMediaTypes"><list><value>text/plain;charset=UTF-8</value></list></property></bean></list></property></bean> </beans>使用Httpclient連接池的方式
<?xml?version="1.0"?encoding="UTF-8"?> <beans?xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beans?http://www.springframework.org/schema/beans/spring-beans.xsd"default-autowire="byName"?default-lazy-init="true"><!--方式二、使用httpclient的實現,帶連接池--><bean?id="ky.pollingConnectionManager"?class="org.apache.http.impl.conn.PoolingHttpClientConnectionManager"><!--整個連接池的并發--><property?name="maxTotal"?value="1000"?/><!--每個主機的并發--><property?name="defaultMaxPerRoute"?value="1000"?/></bean><bean?id="ky.httpClientBuilder"?class="org.apache.http.impl.client.HttpClientBuilder"?factory-method="create"><property?name="connectionManager"?ref="ky.pollingConnectionManager"?/><!--開啟重試--><property?name="retryHandler"><bean?class="org.apache.http.impl.client.DefaultHttpRequestRetryHandler"><constructor-arg?value="2"/><constructor-arg?value="true"/></bean></property><property?name="defaultHeaders"><list><bean?class="org.apache.http.message.BasicHeader"><constructor-arg?value="User-Agent"/><constructor-arg?value="Mozilla/5.0?(Windows?NT?6.1)?AppleWebKit/537.36?(KHTML,?like?Gecko)?Chrome/31.0.1650.16?Safari/537.36"/></bean><bean?class="org.apache.http.message.BasicHeader"><constructor-arg?value="Accept-Encoding"/><constructor-arg?value="gzip,deflate"/></bean><bean?class="org.apache.http.message.BasicHeader"><constructor-arg?value="Accept-Language"/><constructor-arg?value="zh-CN"/></bean></list></property></bean><bean?id="ky.httpClient"?factory-bean="ky.httpClientBuilder"?factory-method="build"?/><bean?id="ky.clientHttpRequestFactory"?class="org.springframework.http.client.HttpComponentsClientHttpRequestFactory"><constructor-arg?ref="ky.httpClient"/><!--連接超時時間,毫秒--><property?name="connectTimeout"?value="5000"/><!--讀寫超時時間,毫秒--><property?name="readTimeout"?value="10000"/></bean><bean?id="restTemplate"?class="org.springframework.web.client.RestTemplate"><constructor-arg?ref="ky.clientHttpRequestFactory"/><property?name="errorHandler"><bean?class="org.springframework.web.client.DefaultResponseErrorHandler"/></property><property?name="messageConverters"><list><bean?class="org.springframework.http.converter.FormHttpMessageConverter"/><bean?class="org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter"/><bean?class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/><bean?class="org.springframework.http.converter.StringHttpMessageConverter"><property?name="supportedMediaTypes"><list><value>text/plain;charset=UTF-8</value></list></property></bean></list></property></bean></beans>?
bean初始化+靜態工具
線程安全的單例(懶漢模式)
基于jdk的spring的RestTemplate
import?org.slf4j.Logger; import?org.slf4j.LoggerFactory; import?org.springframework.context.annotation.Lazy; import?org.springframework.http.client.SimpleClientHttpRequestFactory; import?org.springframework.http.converter.FormHttpMessageConverter; import?org.springframework.http.converter.HttpMessageConverter; import?org.springframework.http.converter.StringHttpMessageConverter; import?org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import?org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter; import?org.springframework.stereotype.Component; import?org.springframework.web.client.DefaultResponseErrorHandler; import?org.springframework.web.client.RestTemplate;import?javax.annotation.PostConstruct; import?java.nio.charset.Charset; import?java.util.ArrayList; import?java.util.List;@Component @Lazy(false) public?class?SimpleRestClient?{private?static?final?Logger?LOGGER?=?LoggerFactory.getLogger(SimpleRestClient.class);private?static?RestTemplate?restTemplate;static?{SimpleClientHttpRequestFactory?requestFactory?=?new?SimpleClientHttpRequestFactory();requestFactory.setReadTimeout(5000);requestFactory.setConnectTimeout(5000);//?添加轉換器List<HttpMessageConverter<?>>?messageConverters?=?new?ArrayList<>();messageConverters.add(new?StringHttpMessageConverter(Charset.forName("UTF-8")));messageConverters.add(new?FormHttpMessageConverter());messageConverters.add(new?MappingJackson2XmlHttpMessageConverter());messageConverters.add(new?MappingJackson2HttpMessageConverter());restTemplate?=?new?RestTemplate(messageConverters);restTemplate.setRequestFactory(requestFactory);restTemplate.setErrorHandler(new?DefaultResponseErrorHandler());LOGGER.info("SimpleRestClient初始化完成");}private?SimpleRestClient()?{}@PostConstructpublic?static?RestTemplate?getClient()?{return?restTemplate;}}使用Httpclient連接池的方式
import?org.apache.http.Header; import?org.apache.http.client.HttpClient; import?org.apache.http.impl.client.DefaultConnectionKeepAliveStrategy; import?org.apache.http.impl.client.DefaultHttpRequestRetryHandler; import?org.apache.http.impl.client.HttpClientBuilder; import?org.apache.http.impl.client.HttpClients; import?org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import?org.apache.http.message.BasicHeader; import?org.slf4j.Logger; import?org.slf4j.LoggerFactory; import?org.springframework.context.annotation.Lazy; import?org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import?org.springframework.http.converter.FormHttpMessageConverter; import?org.springframework.http.converter.HttpMessageConverter; import?org.springframework.http.converter.StringHttpMessageConverter; import?org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import?org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter; import?org.springframework.stereotype.Component; import?org.springframework.web.client.DefaultResponseErrorHandler; import?org.springframework.web.client.RestTemplate;import?javax.annotation.PostConstruct; import?java.nio.charset.Charset; import?java.util.ArrayList; import?java.util.List; import?java.util.concurrent.TimeUnit;@Component @Lazy(false) public?class?RestClient?{private?static?final?Logger?LOGGER?=?LoggerFactory.getLogger(RestClient.class);private?static?RestTemplate?restTemplate;static?{//?長連接保持30秒PoolingHttpClientConnectionManager?pollingConnectionManager?=?new?PoolingHttpClientConnectionManager(30,?TimeUnit.SECONDS);//?總連接數pollingConnectionManager.setMaxTotal(1000);//?同路由的并發數pollingConnectionManager.setDefaultMaxPerRoute(1000);HttpClientBuilder?httpClientBuilder?=?HttpClients.custom();httpClientBuilder.setConnectionManager(pollingConnectionManager);//?重試次數,默認是3次,沒有開啟httpClientBuilder.setRetryHandler(new?DefaultHttpRequestRetryHandler(2,?true));//?保持長連接配置,需要在頭添加Keep-AlivehttpClientBuilder.setKeepAliveStrategy(new?DefaultConnectionKeepAliveStrategy());//????????RequestConfig.Builder?builder?=?RequestConfig.custom(); //????????builder.setConnectionRequestTimeout(200); //????????builder.setConnectTimeout(5000); //????????builder.setSocketTimeout(5000); // //????????RequestConfig?requestConfig?=?builder.build(); //????????httpClientBuilder.setDefaultRequestConfig(requestConfig);List<Header>?headers?=?new?ArrayList<>();headers.add(new?BasicHeader("User-Agent",?"Mozilla/5.0?(Windows?NT?6.1)?AppleWebKit/537.36?(KHTML,?like?Gecko)?Chrome/31.0.1650.16?Safari/537.36"));headers.add(new?BasicHeader("Accept-Encoding",?"gzip,deflate"));headers.add(new?BasicHeader("Accept-Language",?"zh-CN"));headers.add(new?BasicHeader("Connection",?"Keep-Alive"));httpClientBuilder.setDefaultHeaders(headers);HttpClient?httpClient?=?httpClientBuilder.build();//?httpClient連接配置,底層是配置RequestConfigHttpComponentsClientHttpRequestFactory?clientHttpRequestFactory?=?new?HttpComponentsClientHttpRequestFactory(httpClient);//?連接超時clientHttpRequestFactory.setConnectTimeout(5000);//?數據讀取超時時間,即SocketTimeoutclientHttpRequestFactory.setReadTimeout(5000);//?連接不夠用的等待時間,不宜過長,必須設置,比如連接不夠用時,時間過長將是災難性的clientHttpRequestFactory.setConnectionRequestTimeout(200);//?緩沖請求數據,默認值是true。通過POST或者PUT大量發送數據時,建議將此屬性更改為false,以免耗盡內存。//?clientHttpRequestFactory.setBufferRequestBody(false);//?添加內容轉換器List<HttpMessageConverter<?>>?messageConverters?=?new?ArrayList<>();messageConverters.add(new?StringHttpMessageConverter(Charset.forName("UTF-8")));messageConverters.add(new?FormHttpMessageConverter());messageConverters.add(new?MappingJackson2XmlHttpMessageConverter());messageConverters.add(new?MappingJackson2HttpMessageConverter());restTemplate?=?new?RestTemplate(messageConverters);restTemplate.setRequestFactory(clientHttpRequestFactory);restTemplate.setErrorHandler(new?DefaultResponseErrorHandler());LOGGER.info("RestClient初始化完成");}private?RestClient()?{}@PostConstructpublic?static?RestTemplate?getClient()?{return?restTemplate;}} @Configuration public?class?RestConfig?{@Beanpublic?RestTemplate?restTemplate(){RestTemplate?restTemplate?=?new?RestTemplate();return?restTemplate;}@Bean("urlConnection")public?RestTemplate?urlConnectionRestTemplate(){RestTemplate?restTemplate?=?new?RestTemplate(new?SimpleClientHttpRequestFactory());return?restTemplate;}@Bean("httpClient")public?RestTemplate?httpClientRestTemplate(){RestTemplate?restTemplate?=?new?RestTemplate(new?HttpComponentsClientHttpRequestFactory());return?restTemplate;}@Bean("oKHttp3")public?RestTemplate?OKHttp3RestTemplate(){RestTemplate?restTemplate?=?new?RestTemplate(new?OkHttp3ClientHttpRequestFactory());return?restTemplate;} }ErrorHolder
自定義的一個異常結果包裝類
import?org.springframework.http.HttpHeaders; import?org.springframework.http.HttpStatus; import?org.springframework.web.client.HttpClientErrorException; import?org.springframework.web.client.HttpServerErrorException;public?class?ErrorHolder?{private?HttpStatus?statusCode;private?String?statusText;private?String?responseBody;private?HttpHeaders?responseHeaders;public?ErrorHolder(HttpStatus?statusCode,?String?statusText,?String?responseBody)?{this.statusCode?=?statusCode;this.statusText?=?statusText;this.responseBody?=?responseBody;}public?ErrorHolder(String?statusText)?{this.statusText?=?statusText;}public?HttpStatus?getStatusCode()?{return?statusCode;}public?void?setStatusCode(HttpStatus?statusCode)?{this.statusCode?=?statusCode;}public?String?getStatusText()?{return?statusText;}public?void?setStatusText(String?statusText)?{this.statusText?=?statusText;}public?String?getResponseBody()?{return?responseBody;}public?void?setResponseBody(String?responseBody)?{this.responseBody?=?responseBody;}public?HttpHeaders?getResponseHeaders()?{return?responseHeaders;}public?void?setResponseHeaders(HttpHeaders?responseHeaders)?{this.responseHeaders?=?responseHeaders;}public?static?ErrorHolder?build(Exception?exception)?{if?(exception?instanceof?HttpServerErrorException)?{HttpServerErrorException?e?=?(HttpServerErrorException)?exception;return?new?ErrorHolder(e.getStatusCode(),?e.getStatusText(),?e.getResponseBodyAsString());}if?(exception?instanceof?HttpClientErrorException)?{HttpClientErrorException?e?=?(HttpClientErrorException)?exception;return?new?ErrorHolder(e.getStatusCode(),?e.getStatusText(),?e.getResponseBodyAsString());}return?new?ErrorHolder(exception.getMessage());} }使用樣例
api里面可以做自動的參數匹配:如:http://you domainn name/test?empNo={empNo},則下面方法的最后一個參數為數據匹配參數,會自動根據key進行查找,然后替換
API沒有聲明異常,注意進行異常處理
更多使用語法請查看API文檔
ResponseEntity<List<KyArea>>?result?=?RestClient.getClient() .exchange(DIVIDE_PLATE_API,?HttpMethod.GET,?HttpEntity.EMPTY,?new?ParameterizedTypeReference<List<KyArea>>()?{},?map("empNo",?empNo)); List<KyArea>?list?=?result.getBody();ResponseEntity<KyArea>?result?=?RestClient.getClient() .exchange(DIVIDE_PLATE_API,?HttpMethod.GET,?HttpEntity.EMPTY,?KyArea.class,?map("empNo",?empNo)); KyArea?kyArea?=?result.getBody();RestTemplate處理請求狀態碼為非200的返回數據
默認的 RestTemplate 有個機制是請求狀態碼非200 就拋出異常,會中斷接下來的操作。如果不想中斷對結果數據得解析,可以通過覆蓋默認的 ResponseErrorHandler。
見下面的示例,示例中的方法中基本都是空方法,只要對hasError修改下,讓他一直返回true,即是不檢查狀態碼及拋異常了。
@Bean("sslRestTemplate")public?RestTemplate?getRestTemplate()?throws?Exception?{RestTemplate?sslRestTemplate?=?new?RestTemplate(new?HttpsClientRequestFactory());ResponseErrorHandler?responseErrorHandler?=?new?ResponseErrorHandler()?{@Overridepublic?boolean?hasError(ClientHttpResponse?clientHttpResponse)?throws?IOException?{return?true;}@Overridepublic?void?handleError(ClientHttpResponse?clientHttpResponse)?throws?IOException?{}};sslRestTemplate.setErrorHandler(responseErrorHandler);return?sslRestTemplate;}或者,修改resttemplate的源碼,把對應的源碼文件拷貝到自己的項目中,但不推薦。
RestTempate的訪問的超時設置
例如,我用的是Httpclient的連接池,RestTemplate的超時設置依賴HttpClient的內部的三個超時時間設置。
HttpClient內部有三個超時時間設置:連接池獲取可用連接超時,連接超時,讀取數據超時:
1.setConnectionRequestTimeout從連接池中獲取可用連接超時:設置從connect Manager獲取Connection 超時時間,單位毫秒。
HttpClient中的要用連接時嘗試從連接池中獲取,若是在等待了一定的時間后還沒有獲取到可用連接(比如連接池中沒有空閑連接了)則會拋出獲取連接超時異常。
2.連接目標超時connectionTimeout,單位毫秒。
指的是連接目標url的連接超時時間,即客服端發送請求到與目標url建立起連接的最大時間。如果在該時間范圍內還沒有建立起連接,則就拋出connectionTimeOut異常。
如測試的時候,將url改為一個不存在的url:“http://test.com” ,超時時間3000ms過后,系統報出異常:? ?org.apache.commons.httpclient.ConnectTimeoutException:The host did not accept the connection within timeout of 3000 ms
3.等待響應超時(讀取數據超時)socketTimeout ,單位毫秒。
連接上一個url后,獲取response的返回等待時間 ,即在與目標url建立連接后,等待放回response的最大時間,在規定時間內沒有返回響應的話就拋出SocketTimeout。
測試時,將socketTimeout 設置很短,會報等待響應超時。
我遇到的問題,restTemplate請求到一個高可用的服務時,返回的超時時間是設置值的2倍,是因為負載均衡器返回的重定向,導致httpClient底層認為沒有超時,又請求一次,如果負載均衡器下有兩個節點,就耗費connectionTimeout的雙倍時間。
總結
以上是生活随笔為你收集整理的为什么 RestTemplate 那么棒,看这篇就够了!的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 鹅厂是如何使用 Git 的?看这!
- 下一篇: 一个高效的定时任务系统