在微服務都是以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接口主要提供了兩種實現方式
1、一種是SimpleClientHttpRequestFactory,使用J2SE提供的方式(既java.net包提供的方式)創建底層的Http請求連接。
2、一種方式是使用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() { } @PostConstruct public 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-Alive httpClientBuilder.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連接配置,底層是配置RequestConfig HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory(httpClient); // 連接超時 clientHttpRequestFactory.setConnectTimeout(5000); // 數據讀取超時時間,即SocketTimeout clientHttpRequestFactory.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() { } @PostConstruct public static RestTemplate getClient() { return restTemplate; } }
@Configuration public class RestConfig { @Bean public 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() { @Override public boolean hasError(ClientHttpResponse clientHttpResponse) throws IOException { return true; } @Override public void handleError(ClientHttpResponse clientHttpResponse) throws IOException { } }; sslRestTemplate.setErrorHandler(responseErrorHandler); return sslRestTemplate; }
或者,修改resttemplate的源碼,把對應的源碼文件拷貝到自己的項目中,但不推薦。
RestTempate的訪問的超時設置
例如,我用的是Httpclient的連接池,RestTemplate的超時設置依賴HttpClient的內部的三個超時時間設置。
1.setConnectionRequestTimeout從連接池中獲取可用連接超時:設置從connect Manager獲取Connection 超時時間,單位毫秒。
HttpClient中的要用連接時嘗試從連接池中獲取,若是在等待了一定的時間后還沒有獲取到可用連接(比如連接池中沒有空閑連接了)則會拋出獲取連接超時異常。
2.連接目標超時connectionTimeout,單位毫秒。
指的是連接目標url的連接超時時間,即客服端發送請求到與目標url建立起連接的最大時間。如果在該時間范圍內還沒有建立起連接,則就拋出connectionTimeOut異常。
如測試的時候,將url改為一個不存在的url:“

3.等待響應超時(讀取數據超時)socketTimeout ,單位毫秒。
連接上一個url后,獲取response的返回等待時間 ,即在與目標url建立連接后,等待放回response的最大時間,在規定時間內沒有返回響應的話就拋出SocketTimeout。
測試時,將socketTimeout 設置很短,會報等待響應超時。
