接口请求重试策略

接口请求重试,在平常的系统调用之间必不可少,最常用的就是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]}

参考链接


文章作者: HoldDie
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 HoldDie !
评论
 上一篇
Spring-测试类-事务提交 Spring-测试类-事务提交
说到 Junit,测试的小船说翻就翻,一不小心就进了沟里。 项目虽紧,但本着完美的追求,自己写了一个测试类来调一下自己的接口,没想到啊没想到,一切都是那么突然,报错了么?没有,数据返回正确么?正确,数据库数据呢?我擦,数据没动啊? 5980
2018-03-13
下一篇 
MySQL-主键生成策略乱炖 MySQL-主键生成策略乱炖
常在河边走,避免不了 MySQL 主键生成策略,故记录一下集中实现方式。 背景 在复杂的分布式系统中,需要对大量的数据和消息进行唯一标识 对于不同的业务系统之间,随着数据增多,对数据库分表后需要哟一个唯一ID标识数据 设计要点 全局唯一性
2018-03-09
  目录