点击上方 "程序员小乐"关注, 星标或置顶一起成长
每天凌晨00点00分, 第一时间与你相约
每日英文
A friend who understands your tears is much more valuable than a lot of friends who only know your smile.
一个懂你泪水的朋友,胜过一群只懂你笑容的朋友。
每日掏心话
生活本身是简单的,快乐也很简单,一切精彩都蕴含在朴素而简单生活中,想得太复杂,往往感受不到简单的快乐。
来自:duanxz | 责编:乐乐
链接:cnblogs.com/duanxz/p/3510622.html
程序员小乐(ID:study_tech)第 976 次推文 图源:百度往日回顾:因用了Insert into select语句,美女同事被开除了!
正文
在微服务都是以HTTP接口的形式暴露自身服务的,因此在调用远程服务时就必须使用HTTP客户端。我们可以使用JDK原生的URLConnection、Apache的Http Client、Netty的异步HTTP Client, Spring的RestTemplate。但是,用起来最方便、最优雅的还是要属Feign了。这里介绍的是RestTemplate。
RestTemplate是Spring提供的用于访问Rest服务的客户端,RestTemplate提供了多种便捷访问远程Http服务的方法,能够大大提高客户端的编写效率。
调用RestTemplate的默认构造函数,RestTemplate对象在底层通过使用java.net包下的实现创建HTTP 请求,可以通过使用ClientHttpRequestFactory指定不同的HTTP请求方式。
ClientHttpRequestFactory接口主要提供了两种实现方式
一种是SimpleClientHttpRequestFactory,使用J2SE提供的方式(既java.net包提供的方式)创建底层的Http请求连接。
一种方式是使用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框架。
请查看RestTemplate源码了解细节,知其然知其所以然!
RestTemplate默认是使用SimpleClientHttpRequestFactory,内部是调用jdk的HttpConnection,默认超时为-1
@Autowired RestTemplate simpleRestTemplate; @Autowired RestTemplate restTemplate;使用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>线程安全的单例(懒汉模式)
搜索公众号程序员小乐回复关键字“offer”获取算法面试题和答案。
自定义的一个异常结果包装类
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 就抛出异常,会中断接下来的操作。如果不想中断对结果数据得解析,可以通过覆盖默认的 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的源码,把对应的源码文件拷贝到自己的项目中,但不推荐。
搜索公众号程序员小乐回复关键字“Java”获取Java面试题和答案。
例如,我用的是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的双倍时间。
欢迎在留言区留下你的观点,一起讨论提高。如果今天的文章让你有新的启发,欢迎转发分享给更多人。欢迎加入程序员小乐技术交流群,在后台回复“加群”或者“学习”即可。
猜你还想看
阿里、腾讯、百度、华为、京东最新面试题汇集
Spring MVC+Spring+Mybatis实现支付宝支付功能(附完整代码)
压缩20M文件从30秒到1秒的优化过程
如何破解“仅三天可见”的朋友圈?
关注订阅号「程序员小乐」,收看更多精彩内容嘿,你在看吗?