在微服务都是以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接口主要提供了两种实现方式 一种是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框架。
xml配置的方式 请查看RestTemplate源码了解细节,知其然知其所以然! RestTemplate默认是使用SimpleClientHttpRequestFactory,内部是调用jdk的HttpConnection,默认超时为-1
1 2 3 4 @Autowired RestTemplate simpleRestTemplate; @Autowired RestTemplate restTemplate;
基于jdk的spring的RestTemplate
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 <?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" > <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连接池的方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 <?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" > <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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 @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连接池的方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 @Component @Lazy(false) public class RestClient { private static final Logger LOGGER = LoggerFactory.getLogger(RestClient.class); private static RestTemplate restTemplate; static { PoolingHttpClientConnectionManager pollingConnectionManager = new PoolingHttpClientConnectionManager(30 , TimeUnit.SECONDS); pollingConnectionManager.setMaxTotal(1000 ); pollingConnectionManager.setDefaultMaxPerRoute(1000 ); HttpClientBuilder httpClientBuilder = HttpClients.custom(); httpClientBuilder.setConnectionManager(pollingConnectionManager); httpClientBuilder.setRetryHandler(new DefaultHttpRequestRetryHandler(2 , true )); httpClientBuilder.setKeepAliveStrategy(new DefaultConnectionKeepAliveStrategy()); 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(); HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory(httpClient); clientHttpRequestFactory.setConnectTimeout(5000 ); clientHttpRequestFactory.setReadTimeout(5000 ); clientHttpRequestFactory.setConnectionRequestTimeout(200 ); 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; } }
ErrorHolder 自定义的一个异常结果包装类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 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()); } }
RestTemplate处理请求状态码为非200的返回数据 默认的 RestTemplate 有个机制是请求状态码非200 就抛出异常,会中断接下来的操作。如果不想中断对结果数据得解析,可以通过覆盖默认的 ResponseErrorHandler。 见下面的示例,示例中的方法中基本都是空方法,只要对hasError修改下,让他一直返回true,即是不检查状态码及抛异常了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @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; }
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的双倍时间。