在微服务都是以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的内部的三个超时时间设置。

 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的双倍时间。

最新文章

  1. python推荐淘宝物美价廉商品
  2. 推荐10个bootstrap及其他框架的后台管理模板
  3. bootstrap插件学习-bootstrap.tooltip.js
  4. 解决Matlab启动时 &quot; Can&#39;t check 9.0 VCRTs The application has failed to start because……&quot; 的错误
  5. logstash 处理各种时间格式
  6. CSS3圆角详解
  7. 实现一个宽和高都是100像素的div可以用鼠标拖拽移动的效果
  8. Learning Cocos2d-x for WP8(8)——动作Action
  9. HTML5 新元素、HTML5 Canvas
  10. JS基础——入门必备
  11. NlpirParser智能挖掘平台词性标注新算法
  12. 第三方页面嵌入到web项目的方案 之 使用iframe嵌入
  13. 浏览器渲染原理笔记 --《How Browser Work》读后总结
  14. Linux系统GNOME主题安装与Tweaks工具使用
  15. SQL分组求每组最大值问题的解决方法收集 (转载)
  16. 集群部署时的分布式session如何实现
  17. Day 04 if判断,while循环,for循环
  18. Java命令学习系列(四)——jstat
  19. Redis入门很简单之六【Jedis常见操作】
  20. C#阿里云移动推送列表

热门文章

  1. Laravel 5.8: Automatic Policy Resolution
  2. Gradle 如何配置将编译的 JAR 发布到 Archiva 中
  3. 数据结构实验之二叉树七:叶子问题(SDUT 3346)
  4. 《视觉SLAM十四讲》学习日志(二)——初识SLAM
  5. sed基础
  6. ie中打印的问题
  7. csp-s模拟99
  8. &quot;数字经济&quot;云安全共测大赛Web-Writeup
  9. 学PHP应注意的问题与知识点
  10. 源码编译vi过程中进行配置时报“checking if compile and link flags for Python are sane... no: PYTHON DISABLED”怎么办?