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连接池内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • java微信公众号开发(搭建本地测试环境)

    java微信公众号开发(搭建本地测试环境)

    这篇文章主要介绍了java微信公众号开发,主要内容有测试公众号与本地测试环境搭建,需要的朋友可以参考下
    2015-12-12
  • 用SpringMVC编写一个HelloWorld的详细过程

    用SpringMVC编写一个HelloWorld的详细过程

    SpringMVC是Spring的一个后续产品,是Spring的一个子项目<BR>SpringMVC 是 Spring 为表述层开发提供的一整套完备的解决方案,本文我们将用SpringMVC编写一个HelloWorld,文中有详细的编写过程,需要的朋友可以参考下
    2023-08-08
  • Java引用队列和虚引用实例分析

    Java引用队列和虚引用实例分析

    这篇文章主要介绍了Java引用队列和虚引用,结合实例形式分析了java引用队列和虚引用相关概念、原理与使用方法,需要的朋友可以参考下
    2019-08-08
  • java.lang.Void类源码解析

    java.lang.Void类源码解析

    这篇文章主要介绍了java.lang.Void类源码解析的相关内容,对源码中的部分内容进行解释,具有一定参考价值,需要的朋友可以了解下。
    2017-10-10
  • JAVA之读取properties时路径的注意问题

    JAVA之读取properties时路径的注意问题

    这篇文章主要介绍了JAVA之读取properties时路径的注意问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-08-08
  • springboot实现发送QQ邮箱

    springboot实现发送QQ邮箱

    这篇文章主要为大家详细介绍了springboot实现发送QQ邮箱,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-06-06
  • SpringMVC互联网软件架构REST使用详解

    SpringMVC互联网软件架构REST使用详解

    这篇文章主要为大家详细介绍了SpringMVC互联网软件架构REST的相关资料,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-03-03
  • Java中synchronized锁升级的过程

    Java中synchronized锁升级的过程

    本文主要介绍了Java中synchronized锁升级的过程,synchronized相对于早期的synchronized做出了优化,从以前的加锁就是重量级锁优化成了有一个锁升级的过,下文详细内容需要的小伙伴可以参考一下
    2022-05-05
  • Java 中的 BufferedReader 介绍_动力节点Java学院整理

    Java 中的 BufferedReader 介绍_动力节点Java学院整理

    BufferedReader 是缓冲字符输入流。它继承于Reader。接下来通过本文给大家介绍BufferedReader的相关知识,需要的朋友参考下吧
    2017-05-05
  • idea2023创建JavaWeb教程之右键没有Servlet的问题解决

    idea2023创建JavaWeb教程之右键没有Servlet的问题解决

    最近在写一个javaweb项目,但是在IDEA中创建好项目后,在搭建结构的时候创建servlet文件去没有选项,所以这里给大家总结下,这篇文章主要给大家介绍了关于idea2023创建JavaWeb教程之右键没有Servlet问题的解决方法,需要的朋友可以参考下
    2023-10-10

最新评论