• RestTemplate实践(及遇到的问题)


      在微服务都是以HTTP接口的形式暴露自身服务的,因此在调用远程服务时就必须使用HTTP客户端。我们可以使用JDK原生的URLConnection、Apache的Http Client、Netty的异步HTTP Client, Spring的RestTemplate。但是,用起来最方便、最优雅的还是要属Feign了。这里介绍的是RestTemplate。

    什么是RestTemplate?

    RestTemplate是Spring提供的用于访问Rest服务的客户端,

    RestTemplate提供了多种便捷访问远程Http服务的方法,能够大大提高客户端的编写效率。

    调用RestTemplate的默认构造函数,RestTemplate对象在底层通过使用java.net包下的实现创建HTTP 请求,

    可以通过使用ClientHttpRequestFactory指定不同的HTTP请求方式。

    ClientHttpRequestFactory接口主要提供了两种实现方式

    1、一种是SimpleClientHttpRequestFactory,使用J2SE提供的方式(既java.net包提供的方式)创建底层的Http请求连接。

    2、一种方式是使用HttpComponentsClientHttpRequestFactory方式,底层使用HttpClient访问远程的Http服务,使用HttpClient可以配置连接池和证书等信息。

    RestTemplate的核心之一 Http Client。

    目前通过RestTemplate 的源码可知,RestTemplate 可支持多种 Http Client的http的访问,如下所示:

    基于 JDK HttpURLConnection 的 SimpleClientHttpRequestFactory,默认。

    基于 Apache HttpComponents Client 的 HttpComponentsClientHttpRequestFactory

    基于 OkHttp3的OkHttpClientHttpRequestFactory。

    基于 Netty4 的 Netty4ClientHttpRequestFactory。

    其中HttpURLConnection 和 HttpClient 为原生的网络访问类,OkHttp3采用了 OkHttp3的框架,Netty4 采用了Netty框架。

    xml配置的方式

    请查看RestTemplate源码了解细节,知其然知其所以然!

    RestTemplate默认是使用SimpleClientHttpRequestFactory,内部是调用jdk的HttpConnection,默认超时为-1

    基于jdk的spring的RestTemplate

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"
           default-autowire="byName" default-lazy-init="true">
     
        <!--方式一、使用jdk的实现-->
        <bean id="ky.requestFactory" class="org.springframework.http.client.SimpleClientHttpRequestFactory">
            <property name="readTimeout" value="10000"/>
            <property name="connectTimeout" value="5000"/>
        </bean>
     
        <bean id="simpleRestTemplate" class="org.springframework.web.client.RestTemplate">
            <constructor-arg ref="ky.requestFactory"/>
            <property name="messageConverters">
                <list>
                    <bean class="org.springframework.http.converter.FormHttpMessageConverter"/>
                    <bean class="org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter"/>
                    <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/>
                    <bean class="org.springframework.http.converter.StringHttpMessageConverter">
                        <property name="supportedMediaTypes">
                            <list>
                                <value>text/plain;charset=UTF-8</value>
                            </list>
                        </property>
                    </bean>
                </list>
            </property>
        </bean>
    </beans>

    使用Httpclient连接池的方式

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"
           default-autowire="byName" default-lazy-init="true">
     
        <!--方式二、使用httpclient的实现,带连接池-->
        <bean id="ky.pollingConnectionManager" class="org.apache.http.impl.conn.PoolingHttpClientConnectionManager">
            <!--整个连接池的并发-->
            <property name="maxTotal" value="1000" />
            <!--每个主机的并发-->
            <property name="defaultMaxPerRoute" value="1000" />
        </bean>
     
        <bean id="ky.httpClientBuilder" class="org.apache.http.impl.client.HttpClientBuilder" factory-method="create">
            <property name="connectionManager" ref="ky.pollingConnectionManager" />
            <!--开启重试-->
            <property name="retryHandler">
                <bean class="org.apache.http.impl.client.DefaultHttpRequestRetryHandler">
                    <constructor-arg value="2"/>
                    <constructor-arg value="true"/>
                </bean>
            </property>
            <property name="defaultHeaders">
                <list>
                    <bean class="org.apache.http.message.BasicHeader">
                        <constructor-arg value="User-Agent"/>
                        <constructor-arg value="Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.16 Safari/537.36"/>
                    </bean>
                    <bean class="org.apache.http.message.BasicHeader">
                        <constructor-arg value="Accept-Encoding"/>
                        <constructor-arg value="gzip,deflate"/>
                    </bean>
                    <bean class="org.apache.http.message.BasicHeader">
                        <constructor-arg value="Accept-Language"/>
                        <constructor-arg value="zh-CN"/>
                    </bean>
                </list>
            </property>
        </bean>
     
        <bean id="ky.httpClient" factory-bean="ky.httpClientBuilder" factory-method="build" />
     
        <bean id="ky.clientHttpRequestFactory" class="org.springframework.http.client.HttpComponentsClientHttpRequestFactory">
            <constructor-arg ref="ky.httpClient"/>
            <!--连接超时时间,毫秒-->
            <property name="connectTimeout" value="5000"/>
            <!--读写超时时间,毫秒-->
            <property name="readTimeout" value="10000"/>
        </bean>
     
        <bean id="restTemplate" class="org.springframework.web.client.RestTemplate">
            <constructor-arg ref="ky.clientHttpRequestFactory"/>
            <property name="errorHandler">
                <bean class="org.springframework.web.client.DefaultResponseErrorHandler"/>
            </property>
            <property name="messageConverters">
                <list>
                    <bean class="org.springframework.http.converter.FormHttpMessageConverter"/>
                    <bean class="org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter"/>
                    <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/>
                    <bean class="org.springframework.http.converter.StringHttpMessageConverter">
                        <property name="supportedMediaTypes">
                            <list>
                                <value>text/plain;charset=UTF-8</value>
                            </list>
                        </property>
                    </bean>
                </list>
            </property>
        </bean>
     
    </beans>

    bean初始化+静态工具

    线程安全的单例(懒汉模式)

    基于jdk的spring的RestTemplate

    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.context.annotation.Lazy;
    import org.springframework.http.client.SimpleClientHttpRequestFactory;
    import org.springframework.http.converter.FormHttpMessageConverter;
    import org.springframework.http.converter.HttpMessageConverter;
    import org.springframework.http.converter.StringHttpMessageConverter;
    import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
    import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter;
    import org.springframework.stereotype.Component;
    import org.springframework.web.client.DefaultResponseErrorHandler;
    import org.springframework.web.client.RestTemplate;
     
    import javax.annotation.PostConstruct;
    import java.nio.charset.Charset;
    import java.util.ArrayList;
    import java.util.List;
     
    
    @Component
    @Lazy(false)
    public class SimpleRestClient {
     
        private static final Logger LOGGER = LoggerFactory.getLogger(SimpleRestClient.class);
     
        private static RestTemplate restTemplate;
     
        static {
            SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
            requestFactory.setReadTimeout(5000);
            requestFactory.setConnectTimeout(5000);
     
            // 添加转换器
            List<HttpMessageConverter<?>> messageConverters = new ArrayList<>();
            messageConverters.add(new StringHttpMessageConverter(Charset.forName("UTF-8")));
            messageConverters.add(new FormHttpMessageConverter());
            messageConverters.add(new MappingJackson2XmlHttpMessageConverter());
            messageConverters.add(new MappingJackson2HttpMessageConverter());
     
            restTemplate = new RestTemplate(messageConverters);
            restTemplate.setRequestFactory(requestFactory);
            restTemplate.setErrorHandler(new DefaultResponseErrorHandler());
     
            LOGGER.info("SimpleRestClient初始化完成");
        }
     
        private SimpleRestClient() {
     
        }
     
        @PostConstruct
        public static RestTemplate getClient() {
            return restTemplate;
        }
     
    } 

    使用Httpclient连接池的方式

    import org.apache.http.Header;
    import org.apache.http.client.HttpClient;
    import org.apache.http.impl.client.DefaultConnectionKeepAliveStrategy;
    import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
    import org.apache.http.impl.client.HttpClientBuilder;
    import org.apache.http.impl.client.HttpClients;
    import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
    import org.apache.http.message.BasicHeader;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.context.annotation.Lazy;
    import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
    import org.springframework.http.converter.FormHttpMessageConverter;
    import org.springframework.http.converter.HttpMessageConverter;
    import org.springframework.http.converter.StringHttpMessageConverter;
    import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
    import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter;
    import org.springframework.stereotype.Component;
    import org.springframework.web.client.DefaultResponseErrorHandler;
    import org.springframework.web.client.RestTemplate;
     
    import javax.annotation.PostConstruct;
    import java.nio.charset.Charset;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.concurrent.TimeUnit;
     
    
    @Component
    @Lazy(false)
    public class RestClient {
     
        private static final Logger LOGGER = LoggerFactory.getLogger(RestClient.class);
     
        private static RestTemplate restTemplate;
     
        static {
            // 长连接保持30秒
            PoolingHttpClientConnectionManager pollingConnectionManager = new PoolingHttpClientConnectionManager(30, TimeUnit.SECONDS);
            // 总连接数
            pollingConnectionManager.setMaxTotal(1000);
            // 同路由的并发数
            pollingConnectionManager.setDefaultMaxPerRoute(1000);
     
            HttpClientBuilder httpClientBuilder = HttpClients.custom();
            httpClientBuilder.setConnectionManager(pollingConnectionManager);
            // 重试次数,默认是3次,没有开启
            httpClientBuilder.setRetryHandler(new DefaultHttpRequestRetryHandler(2, true));
            // 保持长连接配置,需要在头添加Keep-Alive
            httpClientBuilder.setKeepAliveStrategy(new DefaultConnectionKeepAliveStrategy());
     
    //        RequestConfig.Builder builder = RequestConfig.custom();
    //        builder.setConnectionRequestTimeout(200);
    //        builder.setConnectTimeout(5000);
    //        builder.setSocketTimeout(5000);
    //
    //        RequestConfig requestConfig = builder.build();
    //        httpClientBuilder.setDefaultRequestConfig(requestConfig);
     
            List<Header> headers = new ArrayList<>();
            headers.add(new BasicHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.16 Safari/537.36"));
            headers.add(new BasicHeader("Accept-Encoding", "gzip,deflate"));
            headers.add(new BasicHeader("Accept-Language", "zh-CN"));
            headers.add(new BasicHeader("Connection", "Keep-Alive"));
     
            httpClientBuilder.setDefaultHeaders(headers);
     
            HttpClient httpClient = httpClientBuilder.build();
     
            // httpClient连接配置,底层是配置RequestConfig
            HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
            // 连接超时
            clientHttpRequestFactory.setConnectTimeout(5000);
            // 数据读取超时时间,即SocketTimeout
            clientHttpRequestFactory.setReadTimeout(5000);
            // 连接不够用的等待时间,不宜过长,必须设置,比如连接不够用时,时间过长将是灾难性的
            clientHttpRequestFactory.setConnectionRequestTimeout(200);
            // 缓冲请求数据,默认值是true。通过POST或者PUT大量发送数据时,建议将此属性更改为false,以免耗尽内存。
            // clientHttpRequestFactory.setBufferRequestBody(false);
     
            // 添加内容转换器
            List<HttpMessageConverter<?>> messageConverters = new ArrayList<>();
            messageConverters.add(new StringHttpMessageConverter(Charset.forName("UTF-8")));
            messageConverters.add(new FormHttpMessageConverter());
            messageConverters.add(new MappingJackson2XmlHttpMessageConverter());
            messageConverters.add(new MappingJackson2HttpMessageConverter());
     
            restTemplate = new RestTemplate(messageConverters);
            restTemplate.setRequestFactory(clientHttpRequestFactory);
            restTemplate.setErrorHandler(new DefaultResponseErrorHandler());
     
            LOGGER.info("RestClient初始化完成");
        }
     
        private RestClient() {
     
        }
     
        @PostConstruct
        public static RestTemplate getClient() {
            return restTemplate;
        }
     
    } 
     
    @Configuration
    public class RestConfig {
    
        @Bean
        public RestTemplate restTemplate(){
            RestTemplate restTemplate = new RestTemplate();
            return restTemplate;
        }
    
        @Bean("urlConnection")
        public RestTemplate urlConnectionRestTemplate(){
            RestTemplate restTemplate = new RestTemplate(new SimpleClientHttpRequestFactory());
            return restTemplate;
        }
    
        @Bean("httpClient")
        public RestTemplate httpClientRestTemplate(){
            RestTemplate restTemplate = new RestTemplate(new HttpComponentsClientHttpRequestFactory());
            return restTemplate;
        }
    
        @Bean("oKHttp3")
        public RestTemplate OKHttp3RestTemplate(){
            RestTemplate restTemplate = new RestTemplate(new OkHttp3ClientHttpRequestFactory());
            return restTemplate;
        }
    }

    ErrorHolder

    自定义的一个异常结果包装类

    使用样例

    api里面可以做自动的参数匹配:
    如:http://you domainn name/test?empNo={empNo},则下面方法的最后一个参数为数据匹配参数,会自动根据key进行查找,然后替换

    API没有声明异常,注意进行异常处理

    更多使用语法请查看API文档

    RestTemplate处理请求状态码为非200的返回数据

    默认的 RestTemplate 有个机制是请求状态码非200 就抛出异常,会中断接下来的操作。如果不想中断对结果数据得解析,可以通过覆盖默认的 ResponseErrorHandler ,见下面的示例,示例中的方法中基本都是空方法,只要对hasError修改下,让他一直返回true,即是不检查状态码及抛异常了。

    @Bean("sslRestTemplate")
        public RestTemplate getRestTemplate() throws Exception {
            RestTemplate sslRestTemplate = new RestTemplate(new HttpsClientRequestFactory());
            ResponseErrorHandler responseErrorHandler = new ResponseErrorHandler() {
                @Override
                public boolean hasError(ClientHttpResponse clientHttpResponse) throws IOException {
                    return true;
                }
                @Override
                public void handleError(ClientHttpResponse clientHttpResponse) throws IOException {
    
                }
            };
            sslRestTemplate.setErrorHandler(responseErrorHandler);
            return sslRestTemplate;
        }

    或者,修改resttemplate的源码,把对应的源码文件拷贝到自己的项目中,但不推荐。

    例如,我用的是Httpclient的连接池,RestTemplate的超时设置依赖HttpClient的内部的三个超时时间设置。

     HttpClient内部有三个超时时间设置:连接池获取可用连接超时,连接超时,读取数据超时:
    1.setConnectionRequestTimeout从连接池中获取可用连接超时:设置从connect Manager获取Connection 超时时间,单位毫秒。
    HttpClient中的要用连接时尝试从连接池中获取,若是在等待了一定的时间后还没有获取到可用连接(比如连接池中没有空闲连接了)则会抛出获取连接超时异常。

    2.连接目标超时connectionTimeout,单位毫秒。
     指的是连接目标url的连接超时时间,即客服端发送请求到与目标url建立起连接的最大时间。如果在该时间范围内还没有建立起连接,则就抛出connectionTimeOut异常。
     如测试的时候,将url改为一个不存在的url:“http://test.com” ,超时时间3000ms过后,系统报出异常:    org.apache.commons.httpclient.ConnectTimeoutException:The host did not accept the connection within timeout of 3000 ms 

    3.等待响应超时(读取数据超时)socketTimeout ,单位毫秒。
    连接上一个url后,获取response的返回等待时间 ,即在与目标url建立连接后,等待放回response的最大时间,在规定时间内没有返回响应的话就抛出SocketTimeout。
    测试时,将socketTimeout 设置很短,会报等待响应超时。
     
    我遇到的问题,restTemplate请求到一个高可用的服务是,返回的超时时间是设置值的2倍,是因为负载均衡器返回的重定向,导致httpClient底层认为没有超时,又请求一次,如果负载均衡器下有两个节点,就耗费connectionTimeout的双倍时间。
  • 相关阅读:
    【0】认识 神舟王STM32
    【0】STM32 型号 命名 规则
    【1】STM32 Debug in RAM 在RAM中调试STM32 !!!
    【转】3个普通IO识别22个按键试验
    状态机思路在单片机程序设计中的应用
    关于iOS中UIView类视图的圆角
    静态库.a
    cocoapods的安装和使用,遇到的错误
    网络请求后关于刷新界面UI的问题
    UITextField限制字数方法
  • 原文地址:https://www.cnblogs.com/duanxz/p/3510622.html
Copyright © 2020-2023  润新知