作者:duanxz

来源:cnblogs.com/duanxz/p/3510622.html

在微服务都是以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

@Autowired
RestTemplate simpleRestTemplate;
@Autowired
RestTemplate restTemplate;

基于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() {}@PostConstructpublic 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-AlivehttpClientBuilder.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连接配置,底层是配置RequestConfigHttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);// 连接超时clientHttpRequestFactory.setConnectTimeout(5000);// 数据读取超时时间,即SocketTimeoutclientHttpRequestFactory.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() {}@PostConstructpublic static RestTemplate getClient() {return restTemplate;}}
@Configuration
public class RestConfig {@Beanpublic 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

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

import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.HttpServerErrorException;public class ErrorHolder {private HttpStatus statusCode;private String statusText;private String responseBody;private HttpHeaders responseHeaders;public ErrorHolder(HttpStatus statusCode, String statusText, String responseBody) {this.statusCode = statusCode;this.statusText = statusText;this.responseBody = responseBody;}public ErrorHolder(String statusText) {this.statusText = statusText;}public HttpStatus getStatusCode() {return statusCode;}public void setStatusCode(HttpStatus statusCode) {this.statusCode = statusCode;}public String getStatusText() {return statusText;}public void setStatusText(String statusText) {this.statusText = statusText;}public String getResponseBody() {return responseBody;}public void setResponseBody(String responseBody) {this.responseBody = responseBody;}public HttpHeaders getResponseHeaders() {return responseHeaders;}public void setResponseHeaders(HttpHeaders responseHeaders) {this.responseHeaders = responseHeaders;}public static ErrorHolder build(Exception exception) {if (exception instanceof HttpServerErrorException) {HttpServerErrorException e = (HttpServerErrorException) exception;return new ErrorHolder(e.getStatusCode(), e.getStatusText(), e.getResponseBodyAsString());}if (exception instanceof HttpClientErrorException) {HttpClientErrorException e = (HttpClientErrorException) exception;return new ErrorHolder(e.getStatusCode(), e.getStatusText(), e.getResponseBodyAsString());}return new ErrorHolder(exception.getMessage());}
}

使用样例

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

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

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

ResponseEntity<List<KyArea>> result = RestClient.getClient()
.exchange(DIVIDE_PLATE_API, HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference<List<KyArea>>() {}, map("empNo", empNo));
List<KyArea> list = result.getBody();ResponseEntity<KyArea> result = RestClient.getClient()
.exchange(DIVIDE_PLATE_API, HttpMethod.GET, HttpEntity.EMPTY, KyArea.class, map("empNo", empNo));
KyArea kyArea = result.getBody();

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

默认的 RestTemplate 有个机制是请求状态码非200 就抛出异常,会中断接下来的操作。如果不想中断对结果数据得解析,可以通过覆盖默认的 ResponseErrorHandler。

见下面的示例,示例中的方法中基本都是空方法,只要对hasError修改下,让他一直返回true,即是不检查状态码及抛异常了。

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

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

RestTempate的访问的超时设置

例如,我用的是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的双倍时间。

为什么 RestTemplate 那么棒,看这篇就够了!相关推荐

  1. Spring Cloud入门,看这篇就够了!

    点击▲关注 "中生代技术"   给公众号标星置顶 更多精彩 第一时间直达 概述 首先我给大家看一张图,如果大家对这张图有些地方不太理解的话,我希望你们看完我这篇文章会恍然大悟. 什 ...

  2. 一文详解JavaBean 看这篇就够了

    一文详解JavaBean 看这篇就够了 JavaBean的历史渊源 JavaBean的定义(通俗版) JavaBean应用 < jsp:useBean > < jsp:getProp ...

  3. React入门看这篇就够了

    2019独角兽企业重金招聘Python工程师标准>>> 摘要: 很多值得了解的细节. 原文:React入门看这篇就够了 作者:Random Fundebug经授权转载,版权归原作者所 ...

  4. uiautomation遍历windows所有窗口_万字长文!滑动窗口看这篇就够了!

    大家好,我是小浩.今天是小浩算法 "365刷题计划" 滑动窗口系列 - 整合篇.之前给大家讲解过一些滑动窗口的题目,但未作系统整理. 所以我就出了这个整合合集,整合工作中除了保留原 ...

  5. .NET Core实战项目之CMS 第五章 入门篇-Dapper的快速入门看这篇就够了

    写在前面 上篇文章我们讲了如在在实际项目开发中使用Git来进行代码的版本控制,当然介绍的都是比较常用的功能.今天我再带着大家一起熟悉下一个ORM框架Dapper,实例代码的演示编写完成后我会通过Git ...

  6. .NET Core实战项目之CMS 第二章 入门篇-快速入门ASP.NET Core看这篇就够了

    本来这篇只是想简单介绍下ASP.NET Core MVC项目的(毕竟要照顾到很多新手朋友),但是转念一想不如来点猛的(考虑到急性子的朋友),让你通过本文的学习就能快速的入门ASP.NET Core.既 ...

  7. [译]ASP.NET Core Web API 中使用Oracle数据库和Dapper看这篇就够了

    园子里关于ASP.NET Core Web API的教程很多,但大多都是使用EF+Mysql或者EF+MSSQL的文章.甚至关于ASP.NET Core Web API中使用Dapper+Mysql组 ...

  8. ASP.NET Core WebApi使用Swagger生成api说明文档看这篇就够了

    引言 在使用asp.net core 进行api开发完成后,书写api说明文档对于程序员来说想必是件很痛苦的事情吧,但文档又必须写,而且文档的格式如果没有具体要求的话,最终完成的文档则完全取决于开发者 ...

  9. 史上最全!用Pandas读取CSV,看这篇就够了

    导读:pandas.read_csv接口用于读取CSV格式的数据文件,由于CSV文件使用非常频繁,功能强大,参数众多,因此在这里专门做详细介绍. 作者:李庆辉 来源:大数据DT(ID:hzdashuj ...

  10. python java混合编程_详解java调用python的几种用法(看这篇就够了)

    java调用python的几种用法如下: 在java类中直接执行python语句 在java类中直接调用本地python脚本 使用Runtime.getRuntime()执行python脚本文件(推荐 ...

最新文章

  1. 【读书笔记】C#高级编程 第一章
  2. 一顿骚操作!我将 SQL 耗时从 30248.271s 优化到 0.001s
  3. 修改input标签输入样式
  4. 一起谈.NET技术,从.NET中委托写法的演变谈开去(中):Lambda表达式及其优势...
  5. python降维之时间类型数据的处理_python学习笔记之使用sklearn进行PCA数据降维
  6. 艾伟:ASP.NET实用技巧(一)
  7. bean注入属性_摆脱困境:将属性值注入配置Bean
  8. 上线数天获2400星,这个GitHub项目帮你从头开始学习数据科学
  9. android 获取编译日期,flutter学习笔记(2)android编译,以及如何加快首次编译时间。...
  10. 人脸对齐(五)--ESR算法
  11. ecshop bonuscontroller.php,ECSHOP线下红包不能支持最小订单
  12. java robot api_用java Robot API 模拟实现类似按键精灵功能
  13. 电赛公开课整理(二),电路基础,截图+模电知识【16000字】【原创】
  14. 两波形相位差的计算值_变压器损耗计算公式
  15. 做wifi微信认证服务器,怎么实施微信WiFi认证-微信认证的特点
  16. win7修改计算机名 bat,用cmd改计算机名.bat 无需重启电脑生效
  17. 解决to_csv循环追加数据不会覆盖前面的数据
  18. 1.(group by)如何让group by分组后,每组中的所有数据都显示出来
  19. 批量提取同一文件夹内相同后缀名文件
  20. 举个栗子!Tableau 技巧(197):用 Prep 数据去重的三种场景

热门文章

  1. Python 初学者的最佳学习资源
  2. 使用Github Pages建独立博客
  3. OpenCV中BriefDescriptorExtractor
  4. 深入理解Webpack核心模块Tapable钩子[异步版]
  5. SQLMAP使用笔记
  6. AD域服务器卸载---WindowsServer2012R2
  7. Zabbix 安装部署
  8. Ubuntu下架设FTP服务器(转)
  9. 添加nginx为系统服务(service nginx start/stop/restart)
  10. 一个月时间整理《深入浅出Node.js》