在微服务都是以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可以配置连接池和证书等信息。
xml配置的方式
请查看RestTemplate源码了解细节,知其然知其所以然!
RestTemplate默认是使用SimpleClientHttpRequestFactory,内部是调用jdk的HttpConnection,默认超时为-1
@AutowiredRestTemplate simpleRestTemplate;@AutowiredRestTemplate restTemplate;
基于jdk的spring的RestTemplate
text/plain;charset=UTF-8
使用Httpclient连接池的方式
text/plain;charset=UTF-8
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; /*** @title:基于jdk的spring的RestTemplate* @author:liuxing* @date:2015-05-18 09:35*/@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> 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; /*** @title:使用spring的restTemplate替代httpclient工具* @author:liuxing* @date:2015-05-18 08:48*/@Component@Lazy(false)public class RestClient { private static final Logger LOGGER = LoggerFactory.getLogger(SimpleRestClient.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); Listheaders = 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 > 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; } }
HttpClientUtils
import com.dooioo.commons.Strings;import com.dooioo.framework.SpringContextHolder;import com.dooioo.ky.cache.HttpClientResultCache;import org.apache.commons.collections.MapUtils;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.http.HttpEntity;import org.springframework.util.LinkedMultiValueMap;import org.springframework.util.MultiValueMap; import java.util.Map; /**** 类功能说明:httpclient工具类,基于httpclient 4.x* Title: HttpClientUtils.java* @author 刘兴* @date 2014-3-7 下午7:48:58* @version V1.0*/public class HttpClientUtils { private static final Logger LOGGER = LoggerFactory.getLogger(HttpClientUtils.class); /** * post请求 * @param url * @param formParams * @return */ public static String doPost(String url, MapformParams) { if (MapUtils.isEmpty(formParams)) { return doPost(url); } try { MultiValueMap requestEntity = new LinkedMultiValueMap<>(); formParams.keySet().stream().forEach(key -> requestEntity.add(key, MapUtils.getString(formParams, key, ""))); return RestClient.getClient().postForObject(url, requestEntity, String.class); } catch (Exception e) { LOGGER.error("POST请求出错:{}", url, e); } return Strings.EMPTY; } /** * post请求 * @param url * @return */ public static String doPost(String url) { try { return RestClient.getClient().postForObject(url, HttpEntity.EMPTY, String.class); } catch (Exception e) { LOGGER.error("POST请求出错:{}", url, e); } return Strings.EMPTY; } /** * get请求 * @param url * @return */ public static String doGet(String url) { try { return RestClient.getClient().getForObject(url, String.class); } catch (Exception e) { LOGGER.error("GET请求出错:{}", url, e); } return Strings.EMPTY; } }
ErrorHolder
自定义的一个异常结果包装类
import org.springframework.http.HttpHeaders;import org.springframework.http.HttpStatus;import org.springframework.web.client.HttpClientErrorException;import org.springframework.web.client.HttpServerErrorException; /*** @title:ErrorHolder* @author:liuxing* @date:2015-06-16 11:01*/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里面可以做自动的参数匹配:
如: domainn name/test?empNo={empNo},则下面方法的最后一个参数为数据匹配参数,会自动根据key进行查找,然后替换API没有声明异常,注意进行异常处理
更多使用语法请查看API文档
ResponseEntity
> result = RestClient.getClient().exchange(DIVIDE_PLATE_API, HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference
>() {}, map("empNo", empNo));List list = result.getBody(); ResponseEntity 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() { @Override public boolean hasError(ClientHttpResponse clientHttpResponse) throws IOException { return true; } @Override public void handleError(ClientHttpResponse clientHttpResponse) throws IOException { } }; sslRestTemplate.setErrorHandler(responseErrorHandler); return sslRestTemplate; }
或者,修改resttemplate的源码,把对应的源码文件拷贝到自己的项目中,但不推荐。