接口请求重试,在平常的系统调用之间必不可少,最常用的就是HttpClient
,本文针对于不同的retry
方案作简单介绍。
HttpClient 常用 retry 方案
首先此处只针对于 retry 方法进行讨论,因此构建 httpclient 实例不做过多的介绍,大多都是一样的,来一个 栗子。
public static CloseableHttpClient getHttpClient() {
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
cm.setMaxTotal(MAX_TOTAL);
cm.setDefaultMaxPerRoute(MAX_PERROUTE);
CloseableHttpClient httpClient = HttpClients
.custom()
.setRetryHandler(new StandardHttpRequestRetryHandler())
.setConnectionManager(cm)
.build();
return httpClient;
}
不同 的策略修改的只是 setRetryHandler
字段,以及复杂的会使用 setServiceUnavailableRetryStrategy
字段。
DefaultHttpRequestRetryHandler
- HttpClient 提供的一种默认的实现方式,默认重试次数为3次,不开启
- 可以设置自定义次数,
new DefaultHttpRequestRetryHandler(2,true)
- 比较简单,不适合复杂逻辑
StandardHttpRequestRetryHandler
- 官方提供的一个标准的
retry
方案 - 为了保证接口的幂等性约定
restful
接口必须是:GET、HEAD、PUT、DELETE、OPTIONS、TRACE
- 当 url 错误、后台报错、后台超时等情况的时候,不能进行
retry
HttpRequestRetryHandler
实现的功能满足符合一般需求,但是缺少
retry
间隔时间简单栗子
HttpRequestRetryHandler myRetryHandler = new HttpRequestRetryHandler() { public boolean retryRequest( IOException exception, int executionCount, HttpContext context) { if (executionCount >= 5) { // Do not retry if over max retry count return false; } if (exception instanceof InterruptedIOException) { // Timeout return false; } if (exception instanceof UnknownHostException) { // Unknown host return false; } if (exception instanceof ConnectTimeoutException) { // Connection refused return false; } if (exception instanceof SSLException) { // SSL handshake exception return false; } HttpClientContext clientContext = HttpClientContext.adapt(context); HttpRequest request = clientContext.getRequest(); boolean idempotent = !(request instanceof HttpEntityEnclosingRequest); if (idempotent) { // Retry if the request is considered idempotent return true; } return false; } };
ServiceUnavailableRetryStrategy
当然是拥有上述罗列的各种优点
支持自定义retry时间间隔
简单栗子
public static CloseableHttpClient getHttpClient() { ServiceUnavailableRetryStrategy serviceUnavailableRetryStrategy = new ServiceUnavailableRetryStrategy() { /** * retry逻辑 */ @Override public boolean retryRequest(HttpResponse response, int executionCount, HttpContext context) { if (executionCount <= 3) return true; else return false; } /** * retry间隔时间 */ @Override public long getRetryInterval() { return 2000; } }; PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(); cm.setMaxTotal(MAX_TOTAL); cm.setDefaultMaxPerRoute(MAX_PERROUTE); CloseableHttpClient httpClient = HttpClients.custom().setRetryHandler(new DefaultHttpRequestRetryHandler()) .setConnectionManager(cm).build(); return httpClient; }
RestTemplate 实践
RestTemplate 提供了多种便捷访问远程Http服务的方法,能够大大提高客户端的编写效率。
ClientHttpRequestFactory 接口主要提供了两种实现方式:
- 一种是SimpleClientHttpRequestFactory,使用J2SE提供的方式(既java.net包提供的方式)创建底层的Http请求连接。
- 一种方式是使用HttpComponentsClientHttpRequestFactory方式,底层使用HttpClient访问远程的Http服务,使用HttpClient可以配置连接池和证书等信息。
Xml 配置的方式
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;
/** * @title:基于jdk的spring的RestTemplate * @author:liuxing * @date:2015-05-18 09:35 */
@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 java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
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.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.web.client.DefaultResponseErrorHandler;
import org.springframework.web.client.RestTemplate;
/** * 使用spring的restTemplate替代httpclient工具 */
public class RestClient {
private static final Logger LOGGER = LoggerFactory.getLogger(RestClient.class);
private static RestTemplate restTemplate;
private final static Object syncLock = new Object();
private static void initRestTemplate() {
// 长连接保持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() {
}
public static RestTemplate getClient() {
if(restTemplate == null){
synchronized (syncLock) {
if(restTemplate == null){
initRestTemplate();
}
}
}
return restTemplate;
}
}
HttpClientUtils
import java.util.Map;
import org.apache.commons.collections.MapUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpEntity;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
/** * * 类功能说明:httpclient工具类,基于httpclient 4.x */
public class HttpClientUtils {
private static final Logger LOGGER = LoggerFactory.getLogger(HttpClientUtils.class);
/** * post请求 * @param url * @param formParams * @return */
public static String doPost(String url, Map<String, String> formParams) {
if (MapUtils.isEmpty(formParams)) {
return doPost(url);
}
try {
MultiValueMap<String, String> requestEntity = new LinkedMultiValueMap<>();
formParams.keySet().stream().forEach(key -> requestEntity.add(key, MapUtils.getString(formParams, key, "")));
return RestClient.getClient().postForObject(url, requestEntity, String.class);
} catch (Exception e) {
LOGGER.error("POST请求出错:{}", url, e);
}
return null;
}
/** * post请求 * @param url * @return */
public static String doPost(String url) {
try {
return RestClient.getClient().postForObject(url, HttpEntity.EMPTY, String.class);
} catch (Exception e) {
LOGGER.error("POST请求出错:{}", url, e);
}
return null;
}
/** * get请求 * @param url * @return */
public static String doGet(String url) {
try {
return RestClient.getClient().getForObject(url, String.class);
} catch (Exception e) {
LOGGER.error("GET请求出错:{}", url, e);
}
return null;
}
}
RestTemplate 访问 REST 服务详解
在 RestTemplate 中定义了 11 个独立的操作,它们分别是:
方法 | 描述 |
---|---|
delete() | 在特定的 URL 上对资源执行 HTTP DELETE 操作 |
exchange() | 在 URL 上执行特定的 HTTP 方法,返回的 ResponseEntity 包含了响应体所映射成的对象 |
execute() | 在 URL 上执行特定的 HTTP 方法,返回一个从响应体映射得到的对象 |
getForEntity() | 发送一个 HTTP GET 请求,返回的 ResponseEntity 包含了响应体所映射成的对象 |
getForObject() | 发送一个 HTTP GET 请求,返回根据响应体映射形成的对象 |
postForEntity() | POST 数据到一个 URL,返回的 ResponseEntity 包含了响应体所映射成的对象 |
postForLocation() | POST 数据到一个 URL,返回新创建资源的 URL |
postForObject() | POST 数据到一个 URL,返回根据响应体映射形成的对象 |
put() | PUT 资源到特定的 URL |
headForHeaders() | 发送 HTTP HEAD 请求,返回包含特定资源 URL 的 HTTP 头 |
optionsForAllow() | 发送 HTTP OPTIONS 请求,返回对特定 URL 的 Allow 头信息 |
接下来,我将对常用的几个方法分别介绍。
(1)在项目中添加依赖:
<!-- Jackson对自动解析JSON和XML格式的支持 -->
<dependency>
<groupId>com.fasterxml.jackson.jaxrs</groupId>
<artifactId>jackson-jaxrs-json-provider</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
<!-- HttpClient -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<!-- Fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.46</version>
</dependency>
(2)在项目中注入 RestTemplate:
在注入 RestTemplate 的 bean 的时候,可以通过 ClientHttpRequestFactory 指定 RestTemplate 发起 HTTP 请求的底层实现所采用的类库。对此,ClientHttpRequestFactory 接口主要提供了以下两种实现方法:
i)SimpleClientHttpRequestFactory:
也就是底层使用 java.net 包提供的方式创建 Http 连接请求。示例代码如下:
package cn.zifangsky.springbootdemo.config;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestFactory;
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.web.client.RestTemplate;
@Configuration
public class RestTemplateConfig {
/**
* 返回RestTemplate
* @param factory
* @return
*/
@Bean
public RestTemplate restTemplate(ClientHttpRequestFactory factory){
//消息转换器,一般情况下可以省略,只需要添加相关依赖即可
// 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 restTemplate = new RestTemplate(factory);
// restTemplate.setMessageConverters(messageConverters);
return restTemplate;
}
/**
* ClientHttpRequestFactory接口的第一种实现方式,即:
* SimpleClientHttpRequestFactory:底层使用java.net包提供的方式创建Http连接请求
* @return
*/
@Bean
public SimpleClientHttpRequestFactory simpleClientHttpRequestFactory(){
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setReadTimeout(5000);
requestFactory.setConnectTimeout(5000);
return requestFactory;
}
}
ii)HttpComponentsClientHttpRequestFactory(推荐使用):
也就是底层使用 Httpclient 连接池的方式创建 Http 连接请求。示例代码如下:
package cn.zifangsky.springbootdemo.config;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
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.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicHeader;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestFactory;
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.web.client.RestTemplate;
@Configuration
public class RestTemplateConfig {
/**
* 返回RestTemplate
* @param factory
* @return
*/
@Bean
public RestTemplate restTemplate(ClientHttpRequestFactory factory){
//消息转换器,Spring Boot环境可省略,只需要添加相关依赖即可
// 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 restTemplate = new RestTemplate(factory);
// restTemplate.setMessageConverters(messageConverters);
return restTemplate;
}
/**
* ClientHttpRequestFactory接口的另一种实现方式(推荐使用),即:
* HttpComponentsClientHttpRequestFactory:底层使用Httpclient连接池的方式创建Http连接请求
* @return
*/
@Bean
public HttpComponentsClientHttpRequestFactory httpComponentsClientHttpRequestFactory(){
//Httpclient连接池,长连接保持30秒
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(30, TimeUnit.SECONDS);
//设置总连接数
connectionManager.setMaxTotal(1000);
//设置同路由的并发数
connectionManager.setDefaultMaxPerRoute(1000);
//设置header
List<Header> headers = new ArrayList<Header>();
headers.add(new BasicHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.3; rv:36.0) Gecko/20100101 Firefox/36.04"));
headers.add(new BasicHeader("Accept-Encoding", "gzip, deflate"));
headers.add(new BasicHeader("Accept-Language", "zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3"));
headers.add(new BasicHeader("Connection", "keep-alive"));
//创建HttpClient
HttpClient httpClient = HttpClientBuilder.create()
.setConnectionManager(connectionManager)
.setDefaultHeaders(headers)
.setRetryHandler(new DefaultHttpRequestRetryHandler(3, true)) //设置重试次数
.setKeepAliveStrategy(new DefaultConnectionKeepAliveStrategy()) //设置保持长连接
.build();
//创建HttpComponentsClientHttpRequestFactory实例
HttpComponentsClientHttpRequestFactory requestFactory =
new HttpComponentsClientHttpRequestFactory(httpClient);
//设置客户端和服务端建立连接的超时时间
requestFactory.setConnectTimeout(5000);
//设置客户端从服务端读取数据的超时时间
requestFactory.setReadTimeout(5000);
//设置从连接池获取连接的超时时间,不宜过长
requestFactory.setConnectionRequestTimeout(200);
//缓冲请求数据,默认为true。通过POST或者PUT大量发送数据时,建议将此更改为false,以免耗尽内存
requestFactory.setBufferRequestBody(false);
return requestFactory;
}
}
(3)使用 getForObject() 方法发起 GET 请求:
getForObject() 方法实际上是对 getForEntity() 方法的进一步封装,二者用法类似。 唯一的区别在于 getForObject() 方法只返回所请求类型的对象, 而 getForEntity() 方法会返回请求的对象以及响应的 Header、响应状态码等额外信息。
三个 getForObject() 方法的签名如下:
<T> T getForObject(String url, Class<T> responseType, Object... uriVariables) throws RestClientException;
<T> T getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException;
<T> T getForObject(URI url, Class<T> responseType) throws RestClientException;
示例代码如下:
package cn.zifangsky.SpringBootDemo.controller;
import java.util.HashMap;
import java.util.Map;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.web.client.RestTemplate;
import cn.zifangsky.springbootdemo.config.RestTemplateConfig;
import cn.zifangsky.springbootdemo.config.WebMvcConfig;
import cn.zifangsky.springbootdemo.model.DemoObj;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes={WebMvcConfig.class,RestTemplateConfig.class})
@WebAppConfiguration("src/main/resources")
public class TestRestTemplate {
@Autowired
private RestTemplate restTemplate;
/**
* 测试最基本的Get请求
*/
@Test
public void testGetMethod1(){
DemoObj obj = restTemplate.getForObject("http://127.0.0.1:9090/rest/testJson2?id={1}&name={2}"
, DemoObj.class
, 1,"Tom");
System.out.println(obj);
}
}
上面代码设置请求参数使用了数字占位符,同时 getForObject() 方法的最后一个参数是一个可变长度的参数,用于一一替换前面的占位符。当然,除了这种方式之外,还可以使用 Map 来设置参数,比如:
/**
* 测试Get请求另一种设置参数的方式
*/
@Test
public void testGetMethod2(){
Map<String, String> uriVariables = new HashMap<String, String>();
uriVariables.put("var_id", "1");
uriVariables.put("var_name", "Tom");
DemoObj obj = restTemplate.getForObject("http://127.0.0.1:9090/rest/testJson2?id={var_id}&name={var_name}"
, DemoObj.class
, uriVariables);
System.out.println(obj);
}
运行单元测试之后,最后输出如下:
DemoObj [id=2, name=Tom Ret]
此外需要注意的是,由于上面代码只是简单的单元测试,因此请求 URL 就直接硬编码在代码中了。实际开发则需要将之配置到配置文件或者 Zookeeper、Redis 中。比如这样:
package cn.zifangsky.springbootdemo.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import cn.zifangsky.springbootdemo.model.DemoObj;
@RestController
@RequestMapping("/restTemplate")
public class RestTemplateController {
@Value("${SERVER_URL}")
private String SERVER_URL;
@Autowired
private RestTemplate restTemplate;
@RequestMapping(path="/getDemoObj",produces={MediaType.APPLICATION_JSON_UTF8_VALUE})
public DemoObj getDemoObj(){
DemoObj obj = restTemplate.getForObject(SERVER_URL + "/rest/testXML?id={1}&name={2}"
, DemoObj.class
, 1,"Tom");
return obj;
}
}
(4)使用 getForEntity() 方法发起 GET 请求:
三个 getForEntity() 方法的签名如下:
<T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Object... uriVariables) throws RestClientException;
<T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException;
<T> ResponseEntity<T> getForEntity(URI url, Class<T> responseType) throws RestClientException;
示例代码如下:
/**
* 测试Get请求返回详细信息,包括:响应正文、响应状态码、响应Header等
*/
@Test
public void testGetMethod3(){
ResponseEntity<DemoObj> responseEntity = restTemplate.getForEntity("http://127.0.0.1:9090/rest/testJson2?id={1}&name={2}"
, DemoObj.class
, 1,"Tom");
DemoObj body = responseEntity.getBody();
int statusCodeValue = responseEntity.getStatusCodeValue();
HttpHeaders headers = responseEntity.getHeaders();
System.out.println("responseEntity.getBody():" + body);
System.out.println("responseEntity.getStatusCodeValue():" + statusCodeValue);
System.out.println("responseEntity.getHeaders():" + headers);
}
运行单元测试之后,最后输出如下:
responseEntity.getBody():DemoObj [id=2, name=Tom Ret]
responseEntity.getStatusCodeValue():200
responseEntity.getHeaders():{Date=[Fri, 09 Feb 2018 06:22:28 GMT], Content-Type=[application/json;charset=utf-8], Transfer-Encoding=[chunked]}
(5)使用 postForObject() 方法发起 POST 请求:
在 RestTemplate 中,POST 请求跟 GET 请求类似,也可以使用如下三个方法来请求:
<T> T postForObject(String url, Object request, Class<T> responseType, Object... uriVariables)
throws RestClientException;
<T> T postForObject(String url, Object request, Class<T> responseType, Map<String, ?> uriVariables)
throws RestClientException;
<T> T postForObject(URI url, Object request, Class<T> responseType) throws RestClientException;
示例代码如下:
/**
* 测试最基本的Post请求
*/
@Test
public void testPostMethod1(){
DemoObj request = new DemoObj(1l, "Tim");
DemoObj obj = restTemplate.postForObject("http://127.0.0.1:9090/rest/testJson1"
, request, DemoObj.class);
System.out.println(obj);
}
运行单元测试之后,最后输出如下:
DemoObj [id=2, name=Tim Ret]
(6)使用 postForEntity() 方法发起 POST 请求:
三个 postForEntity() 方法的签名如下:
<T> ResponseEntity<T> postForEntity(String url, Object request, Class<T> responseType, Object... uriVariables)
throws RestClientException;
<T> ResponseEntity<T> postForEntity(String url, Object request, Class<T> responseType, Map<String, ?> uriVariables)
throws RestClientException;
<T> ResponseEntity<T> postForEntity(URI url, Object request, Class<T> responseType) throws RestClientException;
示例代码如下:
/**
* 测试Post请求返回详细信息,包括:响应正文、响应状态码、响应Header等
*/
@Test
public void testPostMethod2(){
DemoObj request = new DemoObj(1l, "Tim");
ResponseEntity<DemoObj> responseEntity = restTemplate.postForEntity("http://127.0.0.1:9090/rest/testJson1"
, request, DemoObj.class);
DemoObj body = responseEntity.getBody();
int statusCodeValue = responseEntity.getStatusCodeValue();
HttpHeaders headers = responseEntity.getHeaders();
System.out.println("responseEntity.getBody():" + body);
System.out.println("responseEntity.getStatusCodeValue():" + statusCodeValue);
System.out.println("responseEntity.getHeaders():" + headers);
}
运行单元测试之后,最后输出如下:
responseEntity.getBody():DemoObj [id=2, name=Tim Ret]
responseEntity.getStatusCodeValue():200
responseEntity.getHeaders():{Date=[Fri, 09 Feb 2018 06:32:02 GMT], Content-Type=[application/json;charset=utf-8], Transfer-Encoding=[chunked]}
(7)使用 exchange() 方法执行指定的 HTTP 请求:
exchange() 方法跟上面的 getForObject()、getForEntity()、postForObject()、postForEntity() 等方法不同之处在于它可以指定请求的 HTTP 类型。示例代码如下:
/**
* 测试Exchange请求
*/
@Test
public void testExchange(){
//设置header
HttpHeaders headers = new HttpHeaders();
headers.add("Content-Type", "application/x-zifangsky");
//设置参数
String requestBody = "1#Converter";
HttpEntity<String> requestEntity = new HttpEntity<String>(requestBody,headers);
ResponseEntity<String> responseEntity = restTemplate.exchange("http://127.0.0.1:9090/convert"
, HttpMethod.POST, requestEntity, String.class);
System.out.println("responseEntity.getBody():" + responseEntity.getBody());
System.out.println("responseEntity.getHeaders():" + responseEntity.getHeaders());
}
运行单元测试之后,最后输出如下:
responseEntity.getBody():{“id”:2,”name”:”Converter Ret”}
responseEntity.getHeaders():{Date=[Fri, 09 Feb 2018 06:42:29 GMT], Content-Type=[application/x-zifangsky], Transfer-Encoding=[chunked]}
参考链接
[ranong项目总结-HttpClient-RetryHandler重试(一)]: http://blog.csdn.net/minicto/article/details/56677420
[Spring RestTemplate 实践]: http://www.voidcn.com/article/p-axdkxvgf-gz.html
[如何使用RestTemplate访问restful服务]: http://blog.csdn.net/weixin_38266411/article/details/70046718
[Spring MVC 中使用 RestTemplate 访问 REST 服务详解]: https://juejin.im/entry/5a9e657ff265da238c3a30bb