springboot中RestTemplate配置HttpClient连接池详解

 更新时间:2023年11月18日 09:11:26   作者:morris131  
这篇文章主要介绍了springboot中RestTemplate配置HttpClient连接池详解,这些Http连接工具,使用起来都比较复杂,如果项目中使用的是Spring框架,可以使用Spring自带的RestTemplate来进行Http连接请求,需要的朋友可以参考下

RestTemplate配置HttpClient连接池

在Java开发中,访问第三方HTTP协议的网络接口,通常使用的连接工具为JDK自带的HttpURLConnection、HttpClient(现在应该称之为HttpComponents)和OKHttp。

这些Http连接工具,使用起来都比较复杂,如果项目中使用的是Spring框架,可以使用Spring自带的RestTemplate来进行Http连接请求。

RestTemplate底层默认的连接方式是Java中的HttpURLConnection,可以使用ClientHttpRequestFactory来指定底层使用不同的HTTP连接方式。

RestTemplate中默认的连接方式

RestTemplate中默认使用的是SimpleClientHttpRequestFactory,我们这里手动创建SimpleClientHttpRequestFactory可以指定连接的超时时间,读数据的超时时间。

package com.morris.user.demo;

import com.morris.user.entity.Order;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

/**
 * restTemplate+httpUrlConnection
 */
@Slf4j
public class RestTemplateDemo1 {

    public static void main(String[] args) {
        SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
        factory.setConnectTimeout(3000);
        factory.setReadTimeout(5000);
        RestTemplate restTemplate = new RestTemplate(factory);
        Order[] orders = restTemplate.getForObject("http://127.0.0.1:8020/order/findOrderByUserId?userId=", Order[].class, 1);
        log.info("orders :{}", orders);
    }

}

SimpleClientHttpRequestFactory底层在创建请求的时候使用的就是HttpURLConnection。

org.springframework.http.client.SimpleClientHttpRequestFactory#createRequest

public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException {
    HttpURLConnection connection = openConnection(uri.toURL(), this.proxy);
    prepareConnection(connection, httpMethod.name());

    if (this.bufferRequestBody) {
        return new SimpleBufferingClientHttpRequest(connection, this.outputStreaming);
    }
    else {
        return new SimpleStreamingClientHttpRequest(connection, this.chunkSize, this.outputStreaming);
    }
}

RestTemplate与HttpClient的结合

只需要在构造RestTemplate实例时传入HttpComponentsClientHttpRequestFactory对象即可。

package com.morris.user.demo;

import com.morris.user.entity.Order;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

/**
 * RestTemplate+HttpClient
 */
@Slf4j
public class RestTemplateDemo2 {

    public static void main(String[] args) {
        HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
        RestTemplate restTemplate = new RestTemplate(factory);
        Order[] orders = restTemplate.getForObject("http://127.0.0.1:8020/order/findOrderByUserId?userId=", Order[].class, 1);
        log.info("orders :{}", orders);
    }

}

HttpComponentsClientHttpRequestFactory底层在创建请求时使用了HttpClient。

org.springframework.http.client.HttpComponentsClientHttpRequestFactory#createRequest

public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException {
    HttpClient client = getHttpClient();

    HttpUriRequest httpRequest = createHttpUriRequest(httpMethod, uri);
    postProcessHttpRequest(httpRequest);
    HttpContext context = createHttpContext(httpMethod, uri);
    if (context == null) {
        context = HttpClientContext.create();
    }

    // Request configuration not set in the context
    if (context.getAttribute(HttpClientContext.REQUEST_CONFIG) == null) {
        // Use request configuration given by the user, when available
        RequestConfig config = null;
        if (httpRequest instanceof Configurable) {
            config = ((Configurable) httpRequest).getConfig();
        }
        if (config == null) {
            config = createRequestConfig(client);
        }
        if (config != null) {
            context.setAttribute(HttpClientContext.REQUEST_CONFIG, config);
        }
    }

    if (this.bufferRequestBody) {
        return new HttpComponentsClientHttpRequest(client, httpRequest, context);
    }
    else {
        return new HttpComponentsStreamingClientHttpRequest(client, httpRequest, context);
    }
}

RestTemplate与HttpClient的在生产环境使用的最佳实践

在构建HttpClient时,经常需要配置很多信息,例如RequestTimeout、ConnectTimeout、SocketTimeout、代理、是否允许重定向、连接池等信息。

在HttpClient,对这些参数进行配置需要使用到RequestConfig类的一个内部类Builder。

这里将这些常用的配置抽取出来放到配置文件中:

package com.morris.user.config;

import lombok.extern.slf4j.Slf4j;
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.BasicHeaderElementIterator;
import org.apache.http.protocol.HTTP;
import org.apache.http.ssl.SSLContextBuilder;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;


@EnableConfigurationProperties(HttpClientConfig.class)
@ConditionalOnClass(RestTemplate.class)
@Configuration
@Slf4j
public class RestTemplateAutoConfiguration {

    @Resource
    private HttpClientConfig httpClientConfig;

    @Bean
    @ConditionalOnClass(CloseableHttpClient.class)
    public RestTemplate httpClientRestTemplate(ClientHttpRequestFactory clientHttpRequestFactory){
        return new RestTemplate(clientHttpRequestFactory);
    }

    @Bean
    @ConditionalOnClass(CloseableHttpClient.class)
    public ClientHttpRequestFactory clientHttpRequestFactory(HttpClient httpClient) {
        HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory();
        clientHttpRequestFactory.setHttpClient(httpClient);
        clientHttpRequestFactory.setConnectTimeout(httpClientConfig.getRequest().getConnectTimeout());
        clientHttpRequestFactory.setReadTimeout(httpClientConfig.getRequest().getReadTimeout());
        clientHttpRequestFactory.setConnectionRequestTimeout(httpClientConfig.getRequest().getConnectionRequestTimeout());
        clientHttpRequestFactory.setBufferRequestBody(httpClientConfig.getRequest().isBufferRequestBody());
        return clientHttpRequestFactory;
    }

    @Bean
    @Primary
    @ConditionalOnClass(CloseableHttpClient.class)
    public HttpClient httpClient() throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException {
        HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
        try {
            // 设置信任SSL访问
            SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, (arg0, arg1) -> true).build();
            httpClientBuilder.setSSLContext(sslContext);
            // 任何主机都不会抛出SSLException异常
            HostnameVerifier hostnameVerifier = NoopHostnameVerifier.INSTANCE;
            SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext, hostnameVerifier);
            Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
                    // 注册HTTP和HTTPS请求
                    .register("http", PlainConnectionSocketFactory.getSocketFactory())
                    .register("https", sslConnectionSocketFactory).build();

            // 使用Httpclient连接池的方式配置
            PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
            poolingHttpClientConnectionManager.setMaxTotal(httpClientConfig.getPool().getMaxTotalConnect());
            poolingHttpClientConnectionManager.setDefaultMaxPerRoute(httpClientConfig.getPool().getMaxConnectPerRoute());
            poolingHttpClientConnectionManager.setValidateAfterInactivity(httpClientConfig.getPool().getValidateAfterInactivity());

            httpClientBuilder.setConnectionManager(poolingHttpClientConnectionManager);
            httpClientBuilder.setRetryHandler(new DefaultHttpRequestRetryHandler(httpClientConfig.getPool().getRetryTimes(), true));
            httpClientBuilder.setKeepAliveStrategy(connectionKeepAliveStrategy());
            return httpClientBuilder.build();
        } catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) {
            log.error("初始化HTTP连接池出错", e);
            throw e;
        }
    }

    /**
     * 配置长连接保持策略
     * @return ConnectionKeepAliveStrategy
     */
    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();
                String param = he.getName();
                String value = he.getValue();
                if (value != null && "timeout".equalsIgnoreCase(param)) {
                    try {
                        return Long.parseLong(value) * 1000;
                    } catch(NumberFormatException error) {
                        log.error("解析长连接过期时间异常", error);
                    }
                }
            }
            HttpHost target = (HttpHost) context.getAttribute(HttpClientContext.HTTP_TARGET_HOST);
            //如果请求目标地址,单独配置了长连接保持时间,使用该配置
            Optional<Map.Entry<String, Integer>> any = Optional.ofNullable(httpClientConfig.getPool().getKeepAliveTargetHost()).orElseGet(HashMap::new)
                    .entrySet().stream().filter(
                            e -> e.getKey().equalsIgnoreCase(target.getHostName())).findAny();
            //否则使用默认长连接保持时间
            return any.map(en -> en.getValue() * 1000L).orElse(httpClientConfig.getPool().getKeepAliveTime() * 1000L);
        };
    }

}

到此这篇关于springboot中RestTemplate配置HttpClient连接池详解的文章就介绍到这了,更多相关RestTemplate配置HttpClient连接池内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 使用@Value注解从配置文件中读取数组

    使用@Value注解从配置文件中读取数组

    这篇文章主要介绍了使用@Value注解从配置文件中读取数组的操作,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-07-07
  • 解决CentOS7中运行jar包报错:xxx(Permission denied)

    解决CentOS7中运行jar包报错:xxx(Permission denied)

    在实际工作我们经常会在linux上运行Spring boot编写的微服务程序,下面这篇文章主要给大家介绍了关于如何解决CentOS7中运行jar包报错:xxx(Permission denied)的相关资料,需要的朋友可以参考下
    2024-02-02
  • Intellij IDEA实现springboot热部署过程解析

    Intellij IDEA实现springboot热部署过程解析

    这篇文章主要介绍了Intellij IDEA实现springboot热部署过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-08-08
  • Maven打jar包的三种方式(小结)

    Maven打jar包的三种方式(小结)

    这篇文章主要介绍了Maven打jar包的三种方式,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-07-07
  • 浅谈基于SpringBoot实现一个简单的权限控制注解

    浅谈基于SpringBoot实现一个简单的权限控制注解

    这篇文章主要介绍了基于SpringBoot实现一个简单的权限控制注解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-01-01
  • 强烈推荐IDEA提高开发效率的必备插件

    强烈推荐IDEA提高开发效率的必备插件

    这篇文章主要介绍了强烈推荐IDEA提高开发效率的必备插件,文中有非常详细的图文示例,对想要提高企业开发效率的小伙伴们有非常好的帮助,需要的朋友可以参考下
    2021-04-04
  • Java实现Redis的集合(set)命令操作

    Java实现Redis的集合(set)命令操作

    这篇文章主要介绍了Java实现Redis的集合(set)命令操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-07-07
  • 基于solr全文检索实现原理(详谈)

    基于solr全文检索实现原理(详谈)

    下面小编就为大家分享一篇基于solr全文检索实现原理详谈,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2017-11-11
  • Mybatis结果集自动映射的实例代码

    Mybatis结果集自动映射的实例代码

    在使用Mybatis时,有的时候我们可以不用定义resultMap,而是直接在<select>语句上指定resultType。这个时候其实就用到了Mybatis的结果集自动映射,下面通过本文给大家分享Mybatis结果集自动映射的实例代码,一起看看吧
    2017-02-02
  • IDEA打开项目所有东西都在报红报错的解决方案

    IDEA打开项目所有东西都在报红报错的解决方案

    这篇文章主要给大家介绍了关于IDEA打开项目所有东西都在报红报错的三个解决方案,文中通过图文介绍的非常详细,对大家学习或者使用idea具有一定的参考学习价值,需要的朋友可以参考下
    2023-06-06

最新评论