RestTemplate介绍使用,以及整合 HttpClient 连接池 配置长连接

最近研究RestTemplate长连接,查询了一些资料,这里记录一下。

目录

1、RestTemplate介绍

2 SpringBoot的restTemplate整合HttpClient连接池及配置

2.1. 为什么要整合HttpClient

2.2. 为什么要使用连接池

2.3. 依赖

3、RestTemplate使用

直接使用

在Spring boot中使用RestTemplate

4、RestTemplate定制

一、单类定制

二、跨类定制

三、应用内定制

5、http连接池

References

自定义例子:

例子1:

例子2

例子3

例子4

参考:


1、RestTemplate介绍

       spring框架提供的RestTemplate类可用于在应用中调用rest服务,它简化了与http服务的通信方式,统一了RESTful的标准,封装了http链接,我们只需要传入url及返回值类型即可。相较于之前常用的httpClient,RestTemplate是一种更优雅的调用RESTful服务的方式。

       RestTemplate默认依赖JDK提供http连接的能力(HttpURLConnection),如果有需要的话也可以通过setRequestFactory方法替换为例如Apache HttpComponentsNettyOkHttp等其它HTTP library。

      

2 SpringBoot的restTemplate整合HttpClient连接池及配置

2.1. 为什么要整合HttpClient

      RestTemplate是Spring自带的一个调用rest服务的客户端,它提供了多种便捷访问远程Http服务的方法,能够大大提高客户端的编写效率。

      RestTemplate默认是使用JDK原生的URLConnection,默认超时为-1, 也就表示是没有超时时间的,这个肯定不能满足复杂情况的使用需求, restTemplate的工厂是支持使用HttpClient和OkHttp来作为客户端实现的

2.2. 为什么要使用连接池

      在调用rest请求时,每次请求都需要和服务端建立连接,也就是三次握手,这是一个费时费力的工作,如果我们需要频繁对一个服务端进行调用,难道需要一直去建立连接吗?
     所以使用连接池,可以避免多次建立连接的操作,节省资源开支

2.3. 依赖

除了spring的基本依赖以外,还需要准备下面几个依赖:

 
      org.apache.httpcomponents
      httpclient
      4.5.7
  



     org.springframework.boot
     spring-boot-configuration-processor
     true
     ${springboot.version}
 

3、RestTemplate使用

直接使用

public class RestTemplateTest {
	public static void main(String[] args) {
		RestTemplate restT = new RestTemplate();
		//通过Jackson JSON processing library直接将返回值绑定到对象
		Quote quote = restT.getForObject("http://gturnquist-quoters.cfapps.io/api/random", Quote.class);  
		String quoteString = restT.getForObject("http://gturnquist-quoters.cfapps.io/api/random", String.class);
		System.out.println(quoteString);
	}
}

Because the Jackson JSON processing library is in the classpath, RestTemplate will use it (via a message converter) to convert the incoming JSON data into a Quote object.

可以看到可以将返回结果放在一个String对象中,也可以直接绑定到一个自定义的对象上,其中Quote如下:

@JsonIgnoreProperties(ignoreUnknown = true) // indicate that any properties not bound in this type should be ignored.
public class Quote {

	private String type;
    private Value value;

    //getters & setters
}

这里有如下两点需要注意(懒得翻译),@JsonIgnoreProperties如果定义了返回结果中没有的属性则忽略,另外属性名需要和返回结果的属性名一致,否则需要使用@JsonProperty注解进行匹配。

  • It’s annotated with @JsonIgnoreProperties from the Jackson JSON processing library to indicate that any properties not bound in this type should be ignored.
  • In order for you to directly bind your data to your custom types, you need to specify the variable name exact same as the key in the JSON Document returned from the API. In case your variable name and key in JSON doc are not matching, you need to use @JsonProperty annotation to specify the exact key of JSON document.

在Spring boot中使用RestTemplate

@SpringBootApplication 
public class HelloSpringBoot {
	public static void main(String[] args) {
	    SpringApplication.run(HelloWorld.class, args);
	}
	
	@Bean
	public RestTemplate restTemplate(RestTemplateBuilder builder) {
		return builder.build();
	}	
}

        注意spring boot并不会自动装配RestTemplate类,因为通常用户都需要一个定制的RestTemplate,因此springboot自动装配了一个RestTemplateBuilder类方便用户定制创建自己的RestTemplate类。

Since RestTemplate instances often need to be customized before being used, Spring Boot does not provide any single auto-configured RestTemplate bean. It does, however, auto-configure a RestTemplateBuilder which can be used to create RestTemplate instances when needed. The auto-configured RestTemplateBuilder will ensure that sensible HttpMessageConverters are applied to RestTemplate instances.

RestTemplateService.java

@Service
public class RestTemplateService {
	@Autowired RestTemplate restTemplate;
	
	public Quote someRestCall(){
		return restTemplate.getForObject("http://gturnquist-quoters.cfapps.io/api/random", Quote.class);
	}	
}

RestTemplateController.java

@RestController
@RequestMapping("/api/rest")
public class RestTemplateController {
	@Autowired 
	private RestTemplateService restTemplateService;
	
	@RequestMapping
	public Object index() {
		return restTemplateService.someRestCall();
	}
}

这是访问http://localhost/api/rest返回以下结果:

{
"type": "success",
"value": {
	"id": 9,
	"quote": "So easy it is to switch container in #springboot."
	}
}

http://gturnquist-quoters.cfapps.io/api/random is a RESTful service that randomly fetches quotes about Spring Boot and returns them as a JSON document.

4、RestTemplate定制

        由于不同的rest服务调用可能需要不同的RestTemplate配置,根据适用范围通常有两种方式进行配置。

一、单类定制

RestTemplateService.java

@Service
public class RestTemplateService {
	private final RestTemplate restTemplate;
	public RestTemplateService(RestTemplateBuilder builder){  //RestTemplateBuilder will be auto-configured
		this.restTemplate = builder.setConnectTimeout(1000).setReadTimeout(1000).build();
	}
	
	public Quote someRestCall(){
		return restTemplate.getForObject("http://gturnquist-quoters.cfapps.io/api/random", Quote.class);
	}	
}

二、跨类定制

BeanConf.class

@Configuration
public class BeanConf {

	@Bean(name = "restTemplateA")
	public RestTemplate restTemplateA(RestTemplateBuilder builder) {
		return builder.basicAuthorization("username", "password")  
            .setConnectTimeout(3000)  
            .setReadTimeout(5000)  
            .rootUri("http://api1.example.com/")  
            .errorHandler(new CustomResponseErrorHandler())  
            .additionalMessageConverters(new CustomHttpMessageConverter())  
            .uriTemplateHandler(new OkHttp3ClientHttpRequestFactory())  
            .build();
	}
	
	@Bean(name = "restTemplateB")
	public RestTemplate restTemplateB(RestTemplateBuilder builder) {
		return builder.basicAuthorization("username", "password")  
            .setConnectTimeout(1000)  
            .setReadTimeout(1000)  
            .rootUri("http://api2.example.com/")  
            .errorHandler(new CustomResponseErrorHandler())  
            .additionalMessageConverters(new CustomHttpMessageConverter())  
            .uriTemplateHandler(new OkHttp3ClientHttpRequestFactory())  
            .build();
	}
}

RestTemplateService.java

@Service
public class RestTemplateService {
	@Resource(name = "restTemplateB")
	private RestTemplate restTemplate;
	
	public Quote someRestCall(){
		return restTemplate.getForObject("http://gturnquist-quoters.cfapps.io/api/random", Quote.class);
	}
}

三、应用内定制

        通过实现RestTemplateCustomizer接口,其中的设置在所有通过RestTemplateBuilder创建的RestTemplate都将生效。

@Component  
public class CustomRestTemplateCustomizer implements RestTemplateCustomizer {  
    @Override  
    public void customize(RestTemplate restTemplate) {  
        new RestTemplateBuilder()  
                .detectRequestFactory(false)  
                .basicAuthorization("username", "password")  
                .uriTemplateHandler(new OkHttp3ClientHttpRequestFactory())  
                .errorHandler(new CustomResponseErrorHandler())  
                .configure(restTemplate);  
    }  
} 

5、http连接池

By default RestTemplate creates new Httpconnection every time and closes the connection once done.If you need to have a connection pooling under rest template then you may use different implementation of the ClientHttpRequestFactory that pools the connections.

        RestTemplate默认不使用连接池,如果想使用则需要一个ClientHttpRequestFactory接口的实现类来池化连接。例如使用HttpComponentsClientHttpRequestFactory

RestTemplate restT = new RestTemplate(new HttpComponentsClientHttpRequestFactory());

注意        ​​​​​​​        HttpComponentsClientHttpRequestFactory 是 org.springframework.http.client.ClientHttpRequestFactory的实现类,它底层使用了Apache HttpComponents HttpClient to create requests.

References

  • spring-boot docs Calling REST services
  • an application that uses Spring’s RestTemplate to retrieve a random Spring Boot quotation
  • Consuming a RESTful Web Service

自定义例子:

例子1:

配置类

package com.zgd.springboot.demo.template.config;

import com.alibaba.fastjson.JSON;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.Header;
import org.apache.http.HeaderElement;
import org.apache.http.HeaderElementIterator;
import org.apache.http.HttpHost;
import org.apache.http.client.HttpClient;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.ConnectionKeepAliveStrategy;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient;
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.apache.http.message.BasicHeaderElementIterator;
import org.apache.http.protocol.HTTP;
import org.apache.http.ssl.SSLContextBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
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.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.stereotype.Component;
import org.springframework.web.client.DefaultResponseErrorHandler;
import org.springframework.web.client.RestTemplate;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import java.nio.charset.Charset;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;

/**
 * @Author: zgd
 * @Date: 2019/3/25 16:53
 * @Description:
 */
@Configuration
@Component
@ConditionalOnClass(value = {RestTemplate.class, CloseableHttpClient.class})
@Data
@Slf4j
public class HttpClientConfig {

  @Autowired
  private HttpClientPoolConfig httpClientPoolConfig;

  @Bean
  public RestTemplate restTemplate() {
    return new RestTemplate();
  }


  /**
   * 创建HTTP客户端工厂
   */
  @Bean(name = "clientHttpRequestFactory")
  public ClientHttpRequestFactory clientHttpRequestFactory() {
    /**
     *  maxTotalConnection 和 maxConnectionPerRoute 必须要配
     */
    if (httpClientPoolConfig.getMaxTotalConnect() <= 0) {
      throw new IllegalArgumentException("invalid maxTotalConnection: " + httpClientPoolConfig.getMaxTotalConnect());
    }
    if (httpClientPoolConfig.getMaxConnectPerRoute() <= 0) {
      throw new IllegalArgumentException("invalid maxConnectionPerRoute: " + httpClientPoolConfig.getMaxConnectPerRoute());
    }
    HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory(httpClient());
    // 连接超时
    clientHttpRequestFactory.setConnectTimeout(httpClientPoolConfig.getConnectTimeout());
    // 数据读取超时时间,即SocketTimeout
    clientHttpRequestFactory.setReadTimeout(httpClientPoolConfig.getReadTimeout());
    // 从连接池获取请求连接的超时时间,不宜过长,必须设置,比如连接不够用时,时间过长将是灾难性的
    clientHttpRequestFactory.setConnectionRequestTimeout(httpClientPoolConfig.getConnectionRequestTimout());
    return clientHttpRequestFactory;
  }

  /**
   * 初始化RestTemplate,并加入spring的Bean工厂,由spring统一管理
   */
  @Bean(name = "httpClientTemplate")
  public RestTemplate restTemplate(ClientHttpRequestFactory factory) {
    return createRestTemplate(factory);
  }

  /**
   * 初始化支持异步的RestTemplate,并加入spring的Bean工厂,由spring统一管理,如果你用不到异步,则无须创建该对象
   * 这个类过时了
   * @return
   */
/*  @Bean(name = "asyncRestTemplate")
  @ConditionalOnMissingBean(AsyncRestTemplate.class)
  public AsyncRestTemplate asyncRestTemplate(RestTemplate restTemplate) {
    final Netty4ClientHttpRequestFactory factory = new Netty4ClientHttpRequestFactory();
    factory.setConnectTimeout(this.connectionTimeout);
    factory.setReadTimeout(this.readTimeout);
    return new AsyncRestTemplate(factory, restTemplate);
  }*/

  /**
   * 配置httpClient
   *
   * @return
   */
  @Bean
  public HttpClient httpClient() {
    HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
    try {
      //设置信任ssl访问
      SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, (arg0, arg1) -> true).build();

      httpClientBuilder.setSSLContext(sslContext);
      HostnameVerifier hostnameVerifier = NoopHostnameVerifier.INSTANCE;
      SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext, hostnameVerifier);
      Registry socketFactoryRegistry = RegistryBuilder.create()
              // 注册http和https请求
              .register("http", PlainConnectionSocketFactory.getSocketFactory())
              .register("https", sslConnectionSocketFactory).build();

      //使用Httpclient连接池的方式配置(推荐),同时支持netty,okHttp以及其他http框架
      PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
      // 最大连接数
      poolingHttpClientConnectionManager.setMaxTotal(httpClientPoolConfig.getMaxTotalConnect());
      // 同路由并发数
      poolingHttpClientConnectionManager.setDefaultMaxPerRoute(httpClientPoolConfig.getMaxConnectPerRoute());
      //配置连接池
      httpClientBuilder.setConnectionManager(poolingHttpClientConnectionManager);
      // 重试次数
      httpClientBuilder.setRetryHandler(new DefaultHttpRequestRetryHandler(httpClientPoolConfig.getRetryTimes(), true));

      //设置默认请求头
      List
headers = getDefaultHeaders();       httpClientBuilder.setDefaultHeaders(headers);       //设置长连接保持策略       httpClientBuilder.setKeepAliveStrategy(connectionKeepAliveStrategy());       return httpClientBuilder.build();     } catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) {       log.error("初始化HTTP连接池出错", e);     }     return null;   }   /**    * 配置长连接保持策略    * @return    */   public ConnectionKeepAliveStrategy connectionKeepAliveStrategy(){     return (response, context) -> {       // Honor 'keep-alive' header       HeaderElementIterator it = new BasicHeaderElementIterator(               response.headerIterator(HTTP.CONN_KEEP_ALIVE));       while (it.hasNext()) {         HeaderElement he = it.nextElement();         log.info("HeaderElement:{}", JSON.toJSONString(he));         String param = he.getName();         String value = he.getValue();         if (value != null && "timeout".equalsIgnoreCase(param)) {           try {             return Long.parseLong(value) * 1000;           } catch(NumberFormatException ignore) {             log.error("解析长连接过期时间异常",ignore);           }         }       }       HttpHost target = (HttpHost) context.getAttribute(               HttpClientContext.HTTP_TARGET_HOST);       //如果请求目标地址,单独配置了长连接保持时间,使用该配置       Optional> any = Optional.ofNullable(httpClientPoolProperties.getKeepAliveTargetHost()).orElseGet(HashMap::new)               .entrySet().stream().filter(               e -> e.getKey().equalsIgnoreCase(target.getHostName())).findAny();       //否则使用默认长连接保持时间       return any.map(en -> en.getValue() * 1000L).orElse(httpClientPoolProperties.getKeepAliveTime() * 1000L);     };   }   /**    * 设置请求头    *    * @return    */   private List
getDefaultHeaders() {     List
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"));     return headers;   }   private RestTemplate createRestTemplate(ClientHttpRequestFactory factory) {     RestTemplate restTemplate = new RestTemplate(factory);     //我们采用RestTemplate内部的MessageConverter     //重新设置StringHttpMessageConverter字符集,解决中文乱码问题     modifyDefaultCharset(restTemplate);     //设置错误处理器     restTemplate.setErrorHandler(new DefaultResponseErrorHandler());     return restTemplate;   }   /**    * 修改默认的字符集类型为utf-8    *    * @param restTemplate    */   private void modifyDefaultCharset(RestTemplate restTemplate) {     List> converterList = restTemplate.getMessageConverters();     HttpMessageConverter converterTarget = null;     for (HttpMessageConverter item : converterList) {       if (StringHttpMessageConverter.class == item.getClass()) {         converterTarget = item;         break;       }     }     if (null != converterTarget) {       converterList.remove(converterTarget);     }     Charset defaultCharset = Charset.forName(httpClientPoolConfig.getCharset());     converterList.add(1, new StringHttpMessageConverter(defaultCharset));   } }


这里有个注意点,就是RestTemplate的bean命名,建议和restTemplate区分开, 这样我们如果需要原生的restTemplate,就用restTemplate,如果用我们自定义的,就用httpClientTemplate

@Resource
private RestTemplate restTemplate;

@Resource
private RestTemplate httpClientTemplate;


连接池的配置类:
这里如果没有上面的spring-boot-configuration-processor这个依赖,会报错

package com.zgd.springboot.demo.template.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import java.util.Map;

/**
 * @Author: zgd
 * @Date: 2019/3/26 19:00
 * @Description:
 */
@Component
@ConfigurationProperties(prefix = "spring.http-client.pool")
@Data
public class HttpClientPoolConfig {

  /**
   * java配置的优先级低于yml配置;如果yml配置不存在,会采用java配置
   */
  /**
   * 连接池的最大连接数
   */
  private int maxTotalConnect ;
  /**
   * 同路由的并发数
   */
  private int maxConnectPerRoute ;
  /**
   * 客户端和服务器建立连接超时,默认2s
   */
  private int connectTimeout = 2 * 1000;
  /**
   * 指客户端从服务器读取数据包的间隔超时时间,不是总读取时间,默认30s
   */
  private int readTimeout = 30 * 1000;

  private String charset = "UTF-8";
  /**
   * 重试次数,默认2次
   */
  private int retryTimes = 2;
  /**
   * 从连接池获取连接的超时时间,不宜过长,单位ms
   */
  private int connectionRequestTimout = 200;
  /**
   * 针对不同的地址,特别设置不同的长连接保持时间
   */
  private Map keepAliveTargetHost;
  /**
   * 针对不同的地址,特别设置不同的长连接保持时间,单位 s
   */
  private int keepAliveTime = 60;

}


application.yml:

spring:
  # yml配置的优先级高于java配置;如果yml配置和java配置同时存在,则yml配置会覆盖java配置
  http-client:
    pool:
      #连接池的最大连接数,0代表不限;如果取0,需要考虑连接泄露导致系统崩溃的后果
      maxTotalConnect: 1000
      #每个路由的最大连接数,如果只调用一个地址,可以将其设置为最大连接数
      maxConnectPerRoute: 200
      # 指客户端和服务器建立连接的超时时间,ms , 最大约21秒,因为内部tcp在进行三次握手建立连接时,默认tcp超时时间是20秒
      connectTimeout: 3000
      # 指客户端从服务器读取数据包的间隔超时时间,不是总读取时间,也就是socket timeout,ms
      readTimeout: 5000
      # 从连接池获取连接的timeout,不宜过大,ms
      connectionRequestTimout: 200
      # 重试次数
      retryTimes: 3
      charset: UTF-8
      # 长连接保持时间 单位s,不宜过长
      keepAliveTime: 10
      # 针对不同的网址,长连接保持的存活时间,单位s,如果是频繁而持续的请求,可以设置小一点,不建议设置过大,避免大量无用连接占用内存资源
      keepAliveTargetHost:
        www.baidu.com: 5
  • maxTotalConnect: 连接池的最大连接数,0代表不限;如果取0,需要考虑连接泄露导致系统崩溃的后果
  • maxConnectPerRoute: 每个路由的最大连接数,如果只调用同一个服务端,可以设置和最大连接数相同,也就是一个路由
  • connectTimeout: 客户端和服务端建立连接的超时时间,这里最大只能是21s,因为操作系统的tcp进行三次握手时,有它自己的超时时间,即便设置100s也是在21s后报错.
  • readTimeout: 也就是socketTime,指的是两个相邻的数据包的间隔超时时间,比如下载一个比较大的文件,就算耗时很长也不会中断,但是如果两次响应时间间隔超过这个值就会报错.

举个例子:
将这个时间设置为2000ms, 然后分别去请求这两个接口,这两个接口总的等待时间都是3000ms

@GetMapping("/sleep")
  public String sleep(@RequestParam(required = false,defaultValue = "3000") Integer mils) {
    log.info("sleep:{}",mils);
    try {
      TimeUnit.MILLISECONDS.sleep(mils);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    return "hello!" + mils;
  }


  @GetMapping("/sleep/on_and_off")
  public void sleepOnAndOff(HttpServletResponse response) {
    log.info("sleepOnAndOff");
    for (int i = 0;i < 10;i++){
      try {
        response.getWriter().println("" + i);
        response.flushBuffer();
        Thread.sleep(300);
      } catch (Exception e) {
        e.printStackTrace();
      }
    }
  }

例子2

RestTemplate配置文件

@Slf4j
@Configuration
public class RestTemplateConfig {

    /**
     * 连接池最大连接数
     */
    private int maxTotal = 500;

    /**
     * 同路由并发数
     */
    private int defaultMaxPerRoute = 50;

    /**
     * 重试次数
     */
    private int retryCount = 3;

    /**
     * 连接超时时间/毫秒 超出该时间抛出connect timeout
     */
    private int connectTimeout = 5000;

    /**
     * 数据读取超时时间(socketTimeout)/毫秒 超出该时间抛出 read timeout
     */
    private int readTimeout = 5000;

    /**
     * 请求连接的超时时间/毫秒  从连接池中获取连接的超时时间,超过该时间未拿到可用连接,会抛出org.apache.http.conn.ConnectionPoolTimeoutException:
     * Timeout waiting for connection from pool
     */
    private int connectionRequestTimeout = 500;

    /**
     * Http连接管理
     */
    @Bean
    public HttpClientConnectionManager httpClientConnectionManager() {
        PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(30, TimeUnit.SECONDS);

        Registry registry = RegistryBuilder.create()
                .register("http", PlainConnectionSocketFactory.getSocketFactory())
                .register("https", SSLConnectionSocketFactory
                        .getSocketFactory()).build();

        // 默认构造就是 注册http和https请求  也可通过上述方式手动添加
        // PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager();
        PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager(
                registry);
        poolingHttpClientConnectionManager.setMaxTotal(maxTotal);
        poolingHttpClientConnectionManager.setDefaultMaxPerRoute(defaultMaxPerRoute);
        return poolingHttpClientConnectionManager;
    }


    /**
     * Http客户端
     */
    @Bean
    public HttpClient httpClient(HttpClientConnectionManager httpClientConnectionManager) {
        HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();


        // 请求配置  可以在Http客户端构建的过程中配置, 也可以在请求工厂类中配置
/*        RequestConfig requestConfig = RequestConfig.custom()
                .setSocketTimeout(readTimeout)
                .setConnectTimeout(connectTimeout)
                .setConnectionRequestTimeout(connectionRequestTimeout)
                .build();*/

        // 请求头参数
        List
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")); headers.add(new BasicHeader("Content-type", "application/json;charset=UTF-8")); return httpClientBuilder // 添加请求配置 // .setDefaultRequestConfig(requestConfig) // 添加Http连接管理 .setConnectionManager(httpClientConnectionManager) // 添加请求头 .setDefaultHeaders(headers) // 保持长连接配置,需要在头添加Keep-Alive .setKeepAliveStrategy(new DefaultConnectionKeepAliveStrategy()) // 设置重试次数 默认是3次 .setRetryHandler(new DefaultHttpRequestRetryHandler(retryCount, true)) .build(); } /** * 客户端请求工厂配置 */ @Bean public ClientHttpRequestFactory clientHttpRequestFactory(HttpClient httpClient) { // 创建org.apache.http.client请求工厂 HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory(httpClient); // 客户端请求配置 clientHttpRequestFactory.setConnectTimeout(connectTimeout); clientHttpRequestFactory.setReadTimeout(readTimeout); clientHttpRequestFactory.setConnectionRequestTimeout(connectionRequestTimeout); return clientHttpRequestFactory; } /* // okhttpclient 构建 @Bean public OkHttpClient okHttpClient(){ OkHttpClient okHttpClient = new OkHttpClient(); okHttpClient.newBuilder() .connectTimeout(connectTimeout,TimeUnit.MILLISECONDS) .readTimeout(readTimeout,TimeUnit.MILLISECONDS) .build(); return okHttpClient; } *//** * 客户端请求工厂配置 *//* @Bean public ClientHttpRequestFactory requestFactory(OkHttpClient okHttpClient) { System.out.println("使用okhttpclient"); // 创建OkHttpClient请求工厂 OkHttp3ClientHttpRequestFactory clientHttpRequestFactory = new OkHttp3ClientHttpRequestFactory(okHttpClient); // 客户端请求配置 clientHttpRequestFactory.setConnectTimeout(connectTimeout); clientHttpRequestFactory.setReadTimeout(readTimeout); clientHttpRequestFactory.setWriteTimeout(connectionRequestTimeout); return clientHttpRequestFactory; } */ @Bean @Primary // 表示优先使用该注解的Bean @LoadBalanced // 让RestTemplate具备负载均衡的能力 (下篇讲) public RestTemplate restTemplate(ClientHttpRequestFactory clientHttpRequestFactory) { // 初始化RestTemplate, 添加默认的类型转换 RestTemplate restTemplate = new RestTemplate(); // 添加请求工厂 此处添加的是org.apache.http.client请求工厂, 如要替换成OkHttpClient请求, 则换成OkHttp3ClientHttpRequestFactory请求工厂 restTemplate.setRequestFactory(clientHttpRequestFactory); // 重新设置 StringHttpMessageConverter 字符集为UTF-8,解决中文乱码问题 List> httpMessageConverterList = restTemplate .getMessageConverters(); HttpMessageConverter httpMessageConverter = null; for (HttpMessageConverter messageConverter : httpMessageConverterList) { if (messageConverter instanceof StringHttpMessageConverter) { httpMessageConverter = messageConverter; break; } } // 先删除 StringHttpMessageConverter 类型转换 if (null != httpMessageConverter) { httpMessageConverterList.remove(httpMessageConverter); } // 添加 StringHttpMessageConverter 类型转换 设置字符集为UTF-8 (该处默认的是 ISO-8859-1 ) httpMessageConverterList.add(1, new StringHttpMessageConverter(StandardCharsets.UTF_8)); // 加入FastJson转换器 httpMessageConverterList.add(new FastJsonHttpMessageConverter()); // 添加请求头拦截器 在请求头添加一些信息如 token等 (可选,不次不用,故不添加) // List clientHttpRequestInterceptorList = new ArrayList<>(); // clientHttpRequestInterceptorList.add(new MyInterceptor()); // restTemplate.setInterceptors(clientHttpRequestInterceptorList); return restTemplate; } }

https://juejin.cn/post/7036338666951671815

例子3

依赖:


      org.apache.httpcomponents
      httpclient
      4.5.7
  



     org.springframework.boot
     spring-boot-configuration-processor
     true
     ${springboot.version}
 

yml文件:

spring:
restTemplate:
    #客户端和服务端建立连接的超时时间,这里最大只能是20s,因为linux操作系统的tcp进行三次握手的默认超时时间是20s,,即便设置100s也是在20s后报错(毫秒)
    connectTimeout: 5_000
    #即socketTime,数据响应读取超时时间,指的是两个相邻的数据包的间隔超时时间(毫秒)
    readTimeout: 120_000
    #连接不够用时从连接池获取连接的等待时间,必须设置。不宜过长,连接不够用时,等待时间过长将是灾难性的(毫秒)
    connectionRequestTimeout: 200
    #RestTemplate默认依赖jdk的HTTP连接工具,也支持使用Httpclient、OkHttp和Netty
    httpclient:
      #httpclient连接池 最大tcp连接数 0代表不限;如果取0,需要考虑连接泄露导致系统崩溃的后果
      maxTotal: 2000
      #设置每一个路由的最大连接数,这里的路由是指IP+PORT,例如连接池大小(MaxTotal)设置为2000,路由连接数设置为1500(DefaultMaxPerRoute),
      #对于www.a.com与www.b.com两个路由来说,发起服务的主机连接到每个路由的最大连接数(并发数)不能超过1500,两个路由的总连接数不能超过2000。
      maxConnectPerRoute: 1000
      #是否允许失败重试
      enableRetry: true
      #连接IO异常时重试次数,默认为3次
      retryTimes: 3
      #长连接保持时间, http1.1都是默认开启长连接的,如果不配置这个时间,连接池会默认永久保持连接.如果是不频繁访问的服务端,长期保持一个无用的连接也会大大占用资源
      #对于需要频繁调用的服务端,我们可以开启长连接,然后将这个保持时间设置小一些,能保证相邻两次请求的长连接都还在就可以了.(毫秒)
      keepAliveTime: 10_000
    # 针对不同的请求地址,可以单独设置不同的长连接存活时间
      keepAliveTargetHosts: "{'127.0.0.1': 5000, '127.0.0.2': 3000}"

配置类:

import org.apache.commons.io.IOUtils;
import org.apache.http.HeaderElement;
import org.apache.http.HeaderElementIterator;
import org.apache.http.HttpHost;
import org.apache.http.client.HttpClient;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.ConnectionKeepAliveStrategy;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
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.BasicHeaderElementIterator;
import org.apache.http.protocol.HTTP;
import org.apache.http.ssl.SSLContexts;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.*;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.ResponseErrorHandler;
import org.springframework.web.client.RestTemplate;

import javax.net.ssl.SSLContext;
import java.io.*;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;
import java.util.*;

/**
 * @version v1.0
 * @description: RestTemplate配置类
 * @author: 47
 */
@Configuration
public class RestTemplateConfig {

    private final static Logger logger = LoggerFactory.getLogger(RestTemplateConfig.class);

    //==============Resttemplate相关参数====================
    @Value("${spring.restTemplate.readTimeout}")
    private int readTimeout;
    @Value("${spring.restTemplate.connectTimeout}")
    private int connectTimeout;
    @Value("${spring.restTemplate.connectionRequestTimeout}")
    private int connectionRequestTimeout;

    //==============Httpclient连接池相关参数====================
    @Value("${spring.restTemplate.httpclient.maxTotal}")
    private int maxTotal;
    @Value("${spring.restTemplate.httpclient.maxConnectPerRoute}")
    private int maxConnectPerRoute;
    @Value("${spring.restTemplate.httpclient.enableRetry}")
    private boolean enableRetry;
    @Value("${spring.restTemplate.httpclient.retryTimes}")
    private int retryTimes;
    @Value("#{${spring.restTemplate.httpclient.keepAliveTargetHosts}}")
    private Map keepAliveTargetHosts;
    @Value("${spring.restTemplate.httpclient.keepAliveTime}")
    private long keepAliveTime;


    /**
     * 配置RestTemplate
     *
     * @param clientHttpRequestFactory
     * @return
     */
    @Bean
    public RestTemplate restTemplate(ClientHttpRequestFactory clientHttpRequestFactory) {
        RestTemplate restTemplate = new RestTemplate(clientHttpRequestFactory);
        //默认使用ISO-8859-1编码,此处修改为UTF-8
        List> messageConverters = restTemplate.getMessageConverters();
        Iterator> iterator = messageConverters.iterator();
        while (iterator.hasNext()) {
            HttpMessageConverter converter = iterator.next();
            if (converter instanceof StringHttpMessageConverter) {
                ((StringHttpMessageConverter) converter).setDefaultCharset(Charset.forName("UTF-8"));
            }
        }
        //设置自定义异常处理
        restTemplate.setErrorHandler(new MyErrorHandler());
        //设置日志拦截器
        restTemplate.getInterceptors().add(new LoggingInterceptor());
        return restTemplate;
    }

    /**
     * 配置httpclient工厂
     *
     * @param httpClient
     * @return
     */
    @Bean
    public ClientHttpRequestFactory clientHttpRequestFactory(HttpClient httpClient) {
        HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
        factory.setHttpClient(httpClient);
        //客户端和服务端建立连接的超时时间,这里最大只能是20s,因为linux操作系统的tcp进行三次握手的默认超时时间是20s,,即便设置100s也是在20s后报错
        factory.setConnectTimeout(connectTimeout); //毫秒
        //即socketTime,数据响应读取超时时间,指的是两个相邻的数据包的间隔超时时间
        factory.setReadTimeout(readTimeout); //毫秒
        //连接不够用时从连接池获取连接的等待时间,必须设置。不宜过长,连接不够用时,等待时间过长将是灾难性的
        factory.setConnectionRequestTimeout(connectionRequestTimeout); //毫秒
        //必须使用BufferingClientHttpRequestFactory这个包装类,否则默认实现只允许读取一次响应流,日志输出那里读取之后,请求调用处再读取时会报错
        BufferingClientHttpRequestFactory bufferingClientHttpRequestFactory = new BufferingClientHttpRequestFactory(factory);
        return bufferingClientHttpRequestFactory;
    }

    /**
     * 配置httpclient
     *
     * @return
     */
    @Bean
    public HttpClient httpClient(ConnectionKeepAliveStrategy connectionKeepAliveStrategy) {
        HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
        SSLContext sslContext = null;
        try {
            sslContext = SSLContexts.custom().loadTrustMaterial(null, (X509Certificate[] var1, String var2) -> true).build();
        } catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException e) {
            logger.error(e.getMessage());
        }
//        httpClientBuilder.setSSLContext(sslContext);
        SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE);
        Registry socketFactoryRegistry = RegistryBuilder.create()
                // 注册http和https请求
                .register("http", PlainConnectionSocketFactory.getSocketFactory())
                .register("https", sslConnectionSocketFactory)
                .build();
        //使用Httpclient连接池的方式配置(推荐),同时支持netty,okHttp以及其他http框架
        PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
        // 最大tcp连接数
        poolingHttpClientConnectionManager.setMaxTotal(maxTotal);
        // 同路由最大tcp连接数
        poolingHttpClientConnectionManager.setDefaultMaxPerRoute(maxConnectPerRoute);
        //配置连接池
        httpClientBuilder.setConnectionManager(poolingHttpClientConnectionManager);
        // 是否允许重试和重试次数
        httpClientBuilder.setRetryHandler(new DefaultHttpRequestRetryHandler(retryTimes, enableRetry));

        //设置默认请求头
//        List
headers = getDefaultHeaders(); // httpClientBuilder.setDefaultHeaders(headers); //设置长连接保持策略 httpClientBuilder.setKeepAliveStrategy(connectionKeepAliveStrategy); return httpClientBuilder.build(); } /** * 配置长连接策略 * * @return */ @Bean public ConnectionKeepAliveStrategy connectionKeepAliveStrategy() { return (response, context) -> { // Honor 'keep-alive' header HeaderElementIterator it = new BasicHeaderElementIterator(response.headerIterator(HTTP.CONN_KEEP_ALIVE)); logger.debug("HeaderElement:{}", it); while (it.hasNext()) { HeaderElement he = it.nextElement(); String param = he.getName(); String value = he.getValue(); if (value != null && "timeout".equalsIgnoreCase(param)) { try { //1.服务器有时候会告诉客户端长连接的超时时间,如果有则设置为服务器的返回值 return Long.parseLong(value) * 1000; } catch (NumberFormatException ignore) { logger.error("解析长连接过期时间异常", ignore); } } } //2.如果服务器没有返回超时时间则采用配置的时间 //a.如果请求目标地址,单独配置了长连接保持时间,使用该配置 b.否则使用配置的的默认长连接保持时间keepAliveTime HttpHost target = (HttpHost) context.getAttribute(HttpClientContext.HTTP_TARGET_HOST); Optional> any = Optional.ofNullable(keepAliveTargetHosts).orElseGet(HashMap::new) .entrySet().stream().filter(e -> e.getKey().equalsIgnoreCase(target.getHostName())).findAny(); return any.map(e -> e.getValue()).orElse(keepAliveTime); }; } /** * 自定义异常处理 */ private class MyErrorHandler implements ResponseErrorHandler { @Override public boolean hasError(ClientHttpResponse response) throws IOException { return !response.getStatusCode().is2xxSuccessful(); } @Override public void handleError(ClientHttpResponse response) throws IOException { //响应状态码错误时不抛出异常 throw new HttpClientErrorException(response.getStatusCode(), response.getStatusText(), response.getHeaders(), IOUtils.toByteArray(response.getBody()), Charset.forName("UTF-8")); } } /** * 日志拦截器 */ private class LoggingInterceptor implements ClientHttpRequestInterceptor { @Override public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { traceRequest(request, body); long start = System.currentTimeMillis(); ClientHttpResponse response = execution.execute(request, body); long end = System.currentTimeMillis(); traceResponse(body, request, response, (end - start) / 1000.0f); return response; } private void traceRequest(HttpRequest request, byte[] body) { StringBuilder log = new StringBuilder(); log.append("\n===========================request begin===========================\n") .append("URI : ").append(request.getURI()).append("\n") .append("Method : ").append(request.getMethod()).append("\n") .append("Headers : ").append(request.getHeaders()).append("\n") .append("Request body: ").append(new String(body, Charset.forName("UTF-8"))).append("\n") .append("===========================request end=============================="); logger.debug(log.toString()); } private void traceResponse(byte[] body, HttpRequest request, ClientHttpResponse response, float time) throws IOException { StringBuilder inputStringBuilder = new StringBuilder(); try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(response.getBody(), "UTF-8"))) { String line = bufferedReader.readLine(); while (line != null) { inputStringBuilder.append(line); line = bufferedReader.readLine(); } } StringBuilder log = new StringBuilder(); if (response.getStatusCode().is2xxSuccessful()) { log.append("\n===========================response begin===========================\n") .append("TIME : ").append(time).append("s").append("\n") .append("URI : ").append(request.getURI()).append("\n") .append("Method : ").append(request.getMethod()).append("\n") .append("Status code : ").append(response.getStatusCode()).append("\n") .append("Headers : ").append(response.getHeaders()).append("\n") .append("Response body: ").append(inputStringBuilder.toString()).append("\n") .append("===========================response end=============================="); logger.debug(log.toString()); } else { log.append("\n===========================response begin===========================\n") .append("TIME : ").append(time).append("s").append("\n") .append("URI : ").append(request.getURI()).append("\n") .append("Method : ").append(request.getMethod()).append("\n") .append("Headers : ").append(request.getHeaders()).append("\n") .append("Request body : ").append(new String(body, Charset.forName("UTF-8"))).append("\n") .append("Status code : ").append(response.getStatusCode()).append("\n") .append("Headers : ").append(response.getHeaders()).append("\n") .append("Response body: ").append(inputStringBuilder.toString()).append("\n") .append("===========================response end=============================="); logger.error(log.toString()); } } private String getLogRules() { String rule = ""; String path = System.getProperty("user.dir"); StringBuffer sb = new StringBuffer(); String logRulesPath = sb.append(path) .append(File.separator) .append("config") .append(File.separator) .append("log-rules") .append(File.separator).append("log-print-rules.json").toString(); FileInputStream fileInputStream = null; try { fileInputStream = new FileInputStream(new File(logRulesPath)); rule = IOUtils.toString(fileInputStream, StandardCharsets.UTF_8.name()); } catch (IOException e) { logger.error("Load rules file error.", e); } finally { if (fileInputStream != null) { try { fileInputStream.close(); } catch (IOException e) { logger.error("Closing stream error.", e); } } } return rule; } } }

Util类:

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.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;

import javax.annotation.PostConstruct;
import java.net.URI;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * @version v1.0
 * @description:
 * @author: 47
 */
@Component
public class RestUtils {

    private static RestTemplate restTemplate;

    @Autowired
    private RestTemplate restTemplateValue;

    @PostConstruct
    public void init() {
        restTemplate = restTemplateValue;
    }

    public static String buildUrl(String hostAddress, String uri) {
        return hostAddress + uri;
    }

    public static String buildUrl(String scheme, String host, int port, String uri) {
        return scheme + "://" + host + ":" + port + uri;
    }

    public static  ResponseEntity get(String uri, Map headers, Map uriVariable, Map queryVariable, Class clazz) {
        return execute(uri, HttpMethod.GET, headers, uriVariable, queryVariable, null, clazz);
    }

    public static  ResponseEntity get(String uri, Map uriVariable, Map queryVariable, Class clazz) {
        return execute(uri, HttpMethod.GET, null, uriVariable, queryVariable, null, clazz);
    }

    public static  ResponseEntity get(String uri, String token, Map queryVariable, Class clazz) {
        Map headers = new HashMap(2);
        headers.put("X-Auth-Token", token);
        return execute(uri, HttpMethod.GET, headers, null, queryVariable, null, clazz);
    }

    public static  ResponseEntity get(String uri, Map queryVariable, Class clazz) {
        return execute(uri, HttpMethod.GET, null, null, queryVariable, null, clazz);
    }

    public static  ResponseEntity get(String uri, String token, Class clazz) {
        Map headers = new HashMap(2);
        headers.put("X-Auth-Token", token);
        return execute(uri, HttpMethod.GET, headers, null, null, null, clazz);
    }

    public static  ResponseEntity get(String uri, Class clazz) {
        return execute(uri, HttpMethod.GET, null, null, null, null, clazz);
    }

    public static  ResponseEntity post(String uri, Map headers, Map uriVariable, Map queryVariable, Object body, Class clazz) {
        return execute(uri, HttpMethod.POST, headers, uriVariable, queryVariable, body, clazz);
    }

    public static  ResponseEntity post(String uri, Map uriVariable, Object body, Class clazz) {
        return execute(uri, HttpMethod.POST, null, uriVariable, null, body, clazz);
    }

    public static  ResponseEntity post(String uri, Object body, Class clazz) {
        return execute(uri, HttpMethod.POST, null, null, null, body, clazz);
    }

    public static  ResponseEntity post(String uri, String token, Map uriVariable, Object body, Class clazz) {
        Map headers = new HashMap(2);
        headers.put("X-Auth-Token", token);
        return execute(uri, HttpMethod.POST, headers, uriVariable, null, body, clazz);
    }

    public static  ResponseEntity post(String uri, String token, Object body, Class clazz) {
        Map headers = new HashMap(2);
        headers.put("X-Auth-Token", token);
        return execute(uri, HttpMethod.POST, headers, null, null, body, clazz);
    }

    public static  ResponseEntity put(String uri, Map headers, Map uriVariable, Map queryVariable, Object body, Class clazz) {
        return execute(uri, HttpMethod.PUT, headers, uriVariable, queryVariable, body, clazz);
    }

    public static  ResponseEntity put(String uri, Map uriVariable, Object body, Class clazz) {
        return execute(uri, HttpMethod.PUT, null, uriVariable, null, body, clazz);
    }

    public static  ResponseEntity put(String uri, String token, Map uriVariable, Object body, Class clazz) {
        Map headers = new HashMap(2);
        headers.put("X-Auth-Token", token);
        return execute(uri, HttpMethod.PUT, headers, uriVariable, null, body, clazz);
    }

    public static  ResponseEntity put(String uri, Object body, Class clazz) {
        return execute(uri, HttpMethod.PUT, null, null, null, body, clazz);
    }

    public static  ResponseEntity put(String uri, String token, Object body, Class clazz) {
        Map headers = new HashMap(2);
        headers.put("X-Auth-Token", token);
        return execute(uri, HttpMethod.PUT, headers, null, null, body, clazz);
    }

    public static  ResponseEntity patch(String uri, Map headers, Map uriVariable, Map queryVariable, Object body, Class clazz) {
        return execute(uri, HttpMethod.PATCH, headers, uriVariable, queryVariable, body, clazz);
    }

    public static  ResponseEntity patch(String uri, Map uriVariable, Object body, Class clazz) {
        return execute(uri, HttpMethod.PATCH, null, uriVariable, null, body, clazz);
    }

    public static  ResponseEntity patch(String uri, String token, Map uriVariable, Object body, Class clazz) {
        Map headers = new HashMap(2);
        headers.put("X-Auth-Token", token);
        return execute(uri, HttpMethod.PATCH, headers, uriVariable, null, body, clazz);
    }

    public static  ResponseEntity patch(String uri, Object body, Class clazz) {
        return execute(uri, HttpMethod.PATCH, null, null, null, body, clazz);
    }

    public static  ResponseEntity patch(String uri, String token, Object body, Class clazz) {
        Map headers = new HashMap(2);
        headers.put("X-Auth-Token", token);
        return execute(uri, HttpMethod.PATCH, headers, null, null, body, clazz);
    }

    public static  ResponseEntity delete(String uri, Map headers, Map uriVariable, Map queryVariable, Class clazz) {
        return execute(uri, HttpMethod.DELETE, headers, uriVariable, queryVariable, null, clazz);
    }

    public static  ResponseEntity delete(String uri, Map uriVariable, Class clazz) {
        return execute(uri, HttpMethod.DELETE, null, uriVariable, null, null, clazz);
    }

    public static  ResponseEntity delete(String uri, Map uriVariable, Object body, Class clazz) {
        return execute(uri, HttpMethod.DELETE, null, uriVariable, null, body, clazz);
    }

    /**
     * HTTP/HTTPS请求
     *
     * @param uri           请求地址
     * @param httpMethod    请求方法
     * @param headers       请求头
     * @param uriVariables  路径参数
     * @param queryVariable 查询参数
     * @param body          请求体
     * @param clazz         返回值类型
     * @return
     */
    private static  ResponseEntity execute(String uri, HttpMethod httpMethod, Map headers,
                                                 Map uriVariables, Map queryVariable, Object body, Class clazz) {
        HttpEntity httpEntity = new HttpEntity(body, buildHeader(headers));
        ResponseEntity responseEntity = restTemplate.exchange(buildUri(uri, uriVariables, queryVariable), httpMethod, httpEntity, clazz);
        return responseEntity;
    }


    /**
     * 组装请求头
     *
     * @param headers
     * @return
     */
    private static HttpHeaders buildHeader(Map headers) {
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.add("Content-Type", "application/json; charset=UTF-8");
        httpHeaders.add("Accept-Charset", "UTF-8");
        httpHeaders.add("Accept", "application/json; charset=UTF-8");
        //设置请求头
        if (headers != null) {
            for (Map.Entry entry : headers.entrySet()) {
                httpHeaders.set(entry.getKey(), entry.getValue());
            }
        }
        return httpHeaders;
    }

    /**
     * 组装查询参数
     *
     * @param uri
     * @param queryVariable
     * @return
     */
    private static URI buildUri(String uri, Map uriVariables, Map queryVariable) {
        UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromUriString(uri);
        if (Objects.nonNull(uriVariables)) {
            uriComponentsBuilder.uriVariables(uriVariables);
        }
        if (queryVariable != null) {
            for (Map.Entry entry : queryVariable.entrySet()) {
                uriComponentsBuilder.queryParam(entry.getKey(), entry.getValue());
            }
        }
        return uriComponentsBuilder.build().encode().toUri();
    }
}

例子4

https://www.cnblogs.com/47Gamer/p/13891579.html

1. 对 httpClient 的属性及常用配置封装 HttpPoolProperties 

package com.example.demo.config;

import lombok.Data;
import org.springframework.stereotype.Component;

@Component
//@ConfigurationProperties(prefix = "http.pool.conn") // 可在配置文件中进行配置
@Data
public class HttpPoolProperties {
    // 最大连接数
    private Integer maxTotal = 20;
    // 同路由并发数
    private Integer defaultMaxPerRoute =20 ;
    private Integer connectTimeout = 2000;
    private Integer connectionRequestTimeout=2000;
    private Integer socketTimeout= 2000;
    // 线程空闲多久后进行校验
    private Integer validateAfterInactivity= 2000;
    // 重试次数
    private Integer retryTimes = 2;

    // 是否开启充实
    private boolean enableRetry = true;
    // 重试的间隔:可实现 ServiceUnavailableRetryStrategy 接口
    private Integer retryInterval= 2000;
}

2.  创建httpClient 连接池,并对RestTemplate 指定httpClient 及连接池

package com.example.demo.util;

import com.example.demo.config.HttpPoolProperties;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.web.client.RestTemplate;

@Configuration
public class HttpClientPoolUtils {

    @Autowired
    private HttpPoolProperties httpPoolProperties;

    /**
     * 首先实例化一个连接池管理器,设置最大连接数、并发连接数
     * @return
     */
    @Bean(name = "httpClientConnectionManager")
    public PoolingHttpClientConnectionManager getHttpClientConnectionManager(){
        Registry registry = RegistryBuilder.create()
                .register("http", PlainConnectionSocketFactory.getSocketFactory())
                .register("https", SSLConnectionSocketFactory.getSocketFactory())
                .build();

        PoolingHttpClientConnectionManager httpClientConnectionManager = new PoolingHttpClientConnectionManager(registry);
        //最大连接数
        httpClientConnectionManager.setMaxTotal(httpPoolProperties.getMaxTotal());
        //并发数
        httpClientConnectionManager.setDefaultMaxPerRoute(httpPoolProperties.getDefaultMaxPerRoute());

        httpClientConnectionManager.setValidateAfterInactivity(httpPoolProperties.getValidateAfterInactivity());

        return httpClientConnectionManager;
    }

    /**
     * 实例化连接池,设置连接池管理器。
     * 这里需要以参数形式注入上面实例化的连接池管理器
     * @param httpClientConnectionManager
     * @return
     */
    @Bean(name = "httpClientBuilder")
    public HttpClientBuilder getHttpClientBuilder(@Qualifier("httpClientConnectionManager")PoolingHttpClientConnectionManager httpClientConnectionManager){

        //HttpClientBuilder中的构造方法被protected修饰,所以这里不能直接使用new来实例化一个HttpClientBuilder,可以使用HttpClientBuilder提供的静态方法create()来获取HttpClientBuilder对象
        HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();

        httpClientBuilder.setConnectionManager(httpClientConnectionManager);

        if (httpPoolProperties.isEnableRetry()){
            // 重试次数
            httpClientBuilder.setRetryHandler(new DefaultHttpRequestRetryHandler(httpPoolProperties.getRetryTimes(), true));
            // 若需要自定义http 的重试策略,可以重新实现ServiceUnavailableRetryStrategy 或 HttpRequestRetryHandler接口,比如对指定异常或制定状态码进行重试,并指定充实的次数。
        }else {
            httpClientBuilder.disableAutomaticRetries();
        }
        // 另外httpClientBuilder 可以设置长连接策略,dns解析器,代理,拦截器以及UserAgent等等。可根据业务需要进行实现

        return httpClientBuilder;
    }


    /* 注入连接池,用于获取httpClient
     * @param httpClientBuilder
     * @return
     */
    @Bean("httpClient")
    public CloseableHttpClient httpClient(@Qualifier("httpClientBuilder") HttpClientBuilder httpClientBuilder){
        return httpClientBuilder.build();
    }

    /**
     * Builder是RequestConfig的一个内部类
     * 通过RequestConfig的custom方法来获取到一个Builder对象
     * 设置builder的连接信息
     * 这里还可以设置proxy,cookieSpec等属性。有需要的话可以在此设置
     * @return
     */
    @Bean(name = "builder")
    public RequestConfig.Builder getBuilder(){
        RequestConfig.Builder builder = RequestConfig.custom();
        return builder.setConnectTimeout(httpPoolProperties.getConnectTimeout()) //连接上服务器(握手成功)的时间,超出抛出connect timeout
                //从连接池中获取连接的超时时间,超时间未拿到可用连接,会抛出org.apache.http.conn.ConnectionPoolTimeoutException: Timeout waiting for connection from pool
                .setConnectionRequestTimeout(httpPoolProperties.getConnectionRequestTimeout())
                //服务器返回数据(response)的时间,超过抛出read timeout
                .setSocketTimeout(httpPoolProperties.getSocketTimeout());
    }

    /**
     * 使用builder构建一个RequestConfig对象
     * @param builder
     * @return
     */
    @Bean
    public RequestConfig getRequestConfig(@Qualifier("builder") RequestConfig.Builder builder){
        return builder.build();
    }

    /**
     * RestTemplate 指定httpClient 及连接池
     * 
     * @param httpClient
     * @return
     */
    @Bean(name = "httpClientTemplate")
    public RestTemplate restTemplate(@Qualifier("httpClient") CloseableHttpClient httpClient) {
        HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
        factory.setHttpClient(httpClient);
        RestTemplate restTemplate = new RestTemplate();
        restTemplate.setRequestFactory(factory);
        restTemplate.getMessageConverters().add(new StringHttpMessageConverter());
        return restTemplate;
    }

}


参考:

Using RestTemplate · jiwenxing/spring-boot-demo Wiki · GitHub

SpringBoot的restTemplate整合HttpClient连接池及配置_zzzgd_666的博客-CSDN博客_resttemplate 连接池

springboot 2.0 整合 RestTemplate 与使用教程_李虹柏的博客-CSDN博客

springboot学习记录之RestTemplate - 简书

SpringBoot RestTemplate 整合 HttpClient 连接池 配置长连接-java黑洞网

RestTemplate Httpclient连接池 - 简书

你可能感兴趣的:(SpringBoot,Java,restful,http)