最近研究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
参考:
spring框架提供的RestTemplate类可用于在应用中调用rest服务,它简化了与http服务的通信方式,统一了RESTful的标准,封装了http链接,我们只需要传入url及返回值类型即可。相较于之前常用的httpClient,RestTemplate是一种更优雅的调用RESTful服务的方式。
RestTemplate默认依赖JDK提供http连接的能力(HttpURLConnection),如果有需要的话也可以通过setRequestFactory
方法替换为例如Apache HttpComponents
、Netty
或OkHttp
等其它HTTP library。
RestTemplate是Spring自带的一个调用rest服务的客户端,它提供了多种便捷访问远程Http服务的方法,能够大大提高客户端的编写效率。
RestTemplate默认是使用JDK原生的URLConnection,默认超时为-1, 也就表示是没有超时时间的,这个肯定不能满足复杂情况的使用需求, restTemplate的工厂是支持使用HttpClient和OkHttp来作为客户端实现的
在调用rest请求时,每次请求都需要和服务端建立连接,也就是三次握手,这是一个费时费力的工作,如果我们需要频繁对一个服务端进行调用,难道需要一直去建立连接吗?
所以使用连接池,可以避免多次建立连接的操作,节省资源开支
除了spring的基本依赖以外,还需要准备下面几个依赖:
org.apache.httpcomponents
httpclient
4.5.7
org.springframework.boot
spring-boot-configuration-processor
true
${springboot.version}
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注解进行匹配。
@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.
由于不同的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);
}
}
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.
配置类
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
举个例子:
将这个时间设置为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();
}
}
}
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
依赖:
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();
}
}
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连接池 - 简书