RestTemplate全网最强总结(永久更新)
@TOC
前言#
目的是在需要的时候以最快的、无 bug、可承载大并发大数据下的标准使用。
可供日常学习、编写脚本、开发时使用。
希望阅读本文后,不会再被任何 http、接口请求类的问题吓到!有问题找 blog,解决不了请留言,会及时补充,永久更新!。
整体架构图#
基本使用与原理#
RestTemplate 主要方法#
http 方法 | RestTemplate 方法 |
---|---|
GET | getForObject(String url, Class responseType,Object… urlVariables) |
POST | postForObject(String url, Object request,Class responseType,Object… urlVariables) |
PUT | put(String url, Object request,Object… urlVariables) |
DELETE | delete(String url,Object… urlVariables) |
HEAD | headForHeaders(String url,Map<String,?> urlVariables) |
OPTIONS | optionsForAllow(String url,Object… urlVariables) |
推荐这些方法我们都不记,只记录一个:exchange |
URI 与参数#
URL#
- 创建 URL 使用 Uri Builder 类,常见的方法有:
public static UriComponentsBuilder newInstance();
public static UriComponentsBuilder fromUri(URI uri);
public static UriComponentsBuilder fromHttpRequest(HttpRequest request);
public static UriComponentsBuilder fromUriString(String uri);
其中最常用的为 fromUriString,本文以此方法为主。
原理如截图,通过给定的 url,使用正则表达式,分块匹配 url 字符串中的每一部分,得到 scheme,host,port… 等信息,最后 build 到对象属性中。
示例:
// 使用newInstance方法构建,不推荐
UriComponents uriComponents = UriComponentsBuilder.newInstance()
.scheme("http")
.host("www.test2.com")
.path("/test")
.queryParam("query", "a")
.build();
// 使用fromUriString,强烈推荐
String url = UriComponentsBuilder.fromUriString("http://www.baidu.com/test?query1=1").build();
- UriComponentsBuilder 常用方法:
方法 | 介绍 |
---|---|
queryParam | 添加 get 参数,如果存在则替换,否则新增。例子:queryParam (“query2”, 2) |
replaceQueryParam | 替换 get 参数,与 queryParam 类似,为了增强语意 |
queryParams | 批量增加 get 参数,入参是值 一个 Key 对应多个 Value 的 MultiValueMap |
replaceQueryParams | 先清空参数列表,再批量增加 get 参数,入参是 MultiValueMap |
expand | 替换所有 url 占位符 |
encode | 对 url 字符串进行编码,默认是 utf-8,编码格式可以在参数中指定 |
toUriString | 输出 url 最终字符串,会自动调用 encode 进行编码 |
toString | 输出 url 原始字符串,不会自动调用 encode 进行编码 |
对于编码此处特殊代码说明: |
/**
* 情况一
*/
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString("http://www.test2.com/test?query=哈哈");
// 可以编码成功
String s = builder.toUriString();
/**
* 情况二
*/
UriComponents uriComponents = UriComponentsBuilder.newInstance()
.scheme("http")
.host("www.test2.com")
.path("/test")
.queryParam("query", "a")
.build();
// 编码失败
String s = uriComponents.toUriString();
// 正确的方式,需要使用builder类的toUriString(),使用UriComponents类的toUriString不起作用,可以用encode手动实现
String s = uriComponents.encode().toString();
// 总结,没事别用newInstance构造,老老实实使用fromUriString
- UriComponents 类
可以理解为是一个 Uri 的构造体,可以通过此类拿到想要的 uri 部件中的各种信息,如 host、queryparam 等。可以通过 UriComponentsBuilder.build 方法获取 UriComponents。并通过 UriComponents.encode.toString () 方法对 url 进行编码,效果为 urlenconde。常用的为简化版:
UriComponents.toUriString();
常用方法:
public void test01(){
UriComponentsBuilder u = UriComponentsBuilder.fromUriString("http://www.test.com/{grd}/test?query1=1");
UriComponents uriComponents = u.build().expand("heihei");
String s = uriComponents.toUriString();
String query = uriComponents.getQuery();
MultiValueMap<String, String> queryParams = uriComponents.getQueryParams();
String getPath = uriComponents.getPath();
List<String> getPathSegments = uriComponents.getPathSegments();
String getUserInfo = uriComponents.getUserInfo();
String getFragment = uriComponents.getFragment();
System.out.println(s);
System.out.println("query:"+query);
System.out.println("queryParams"+queryParams);
System.out.println("getPath:"+getPath);
System.out.println("getPathSegments:"+getPathSegments);
}
// 输出结果
http://www.test.com/heihei/test?query1=1
query:query1=1
queryParams{query1=[1]}
getPath:/heihei/test
getPathSegments:[heihei, test]
参数#
每一个方法,第一个参数都是 uri,uri 除了手写,都可以通过 URL 类去构建。
参数可以用数组传递进去(数组指的是动态参数),也可以用 Map 代替。
// 动态数组
String result = restTemplate.getForObject("http://aaa.com/test/{param1}/tt/{param2}", String.class, "11", "22");
// Map
Map<String, String> vars = new HashMap<String, String>();
vars.put("param1", "11");
vars.put("param2", "22");
String result = restTemplate.getForObject("http://aaa.com/test/{param1}/tt/{param2", String.class, vars);
HttpEntity 与 RequestEntity#
HttpEntity#
HttpEntity 对 header 与 body 进行了封装,它的结构体非常简单:
public class HttpEntity<T> {
private final HttpHeaders headers;
private final T body;
}
其中 HttpHeaders 是一个 MultiValueMap<String, String>,总结如下:
- 几乎所有的 header 都封装了常量,所以以后设置 header 不用再傻傻的拼写了,直接在此类搜索。如 HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS 等
- 注意字符集编码不要傻傻的写 UTF_8 了,用常量 StandardCharsets
- 此类还提供了一个 BasicAuth 的小实现:HttpHeaders.encodeBasicAuth (“gurundong”, “123456”, StandardCharsets.UTF_8);
- HttpEntity.EMPTY,创建一个空的 HttpEntity,主要用于在 exchange 方法中使用,简写。
- 示例代码:
public void test01(){
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add(HttpHeaders.ACCEPT,"13");
httpHeaders.add(HttpHeaders.ACCEPT_CHARSET,"24");
System.out.println(httpHeaders.toString());
}
RequestEntity#
RequestEntity extends HttpEntity,又新封装了 HttpMethod 与 URI 属性。实际配合 exchange 使用时,个人感觉直接用 HttpEntity 更好,代码更直观。
ClientHttpRequestFactory#
RestTemplate 集成了 HttpAccessor 基础抽象类,在 HttpAccessor 中,定义了
requestFactory 默认实现。也可以通过 RestTemplate (ClientHttpRequestFactory requestFactory) 的构造函数,传入自定义的 ClientHttpRestFactory。
HttpUrlConnection 实现#
- SimpleClientHttpRequestFactory 的实现,底层为 JDK 自带的 HttpURLConnection,每执行一次 exchange 方法,都会新开一个 tcp 链接句柄。 请求 <=> tcp 链接一一对应。linux 默认的 tcp 链接句柄限制是 1024 (虽然可以通过配置调大),这会极大的限制并发,不能够复用 tcp 通道,浪费大量的 tcp 链接。
private ClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
HttpClinet 实现#
除了 JDK 自带 HttpURLConnection 实现的 SimpleClientHttpRequestFactory,其它常用的还有 HttpComponentsClientHttpRequestFactory、OkHttp3ClientHttpRequestFactory 等。我们只看由 HttpClient 实现的 HttpComponentsClientHttpRequestFactory。
- HttpComponentsClientHttpRequestFactory 有几种构造方法,其中最常用的有无参构造方法、以及有参 (HttpClient httpClient) 构造方法。在生产环境,一定不可使用无参构方法,因为不配置会使用配置配置,由 httpClient 构建的默认连接池会非常小 (默认最大连接是 20,每个路由最大连接是 2)。
- 此处简单小补一下 HttpClient 的连接池原理图:
连接池配置#
- 附上 http 自定义 HttpClient 连接池的代码,以后方便直接使用
@Configuration
public class RestTemplateConfig {
Logger logger = LoggerFactory.getLogger(RestTemplateConfig.class);
@Bean
public RestTemplate restTemplate() {
return new RestTemplate(clientHttpRequestFactory());
}
@Bean
public ClientHttpRequestFactory clientHttpRequestFactory() {
/**
* 链接配置
*/
RequestConfig.Builder configBuilder = RequestConfig.custom();
// 设置连接超时,链接到目标接口 30秒
configBuilder.setConnectTimeout(30000);
// 设置读取超时,等待目标接口超时 60秒
configBuilder.setSocketTimeout(60000);
// 设置从连接池获取连接实例的超时,等待连接池可用链接超时 10秒
configBuilder.setConnectionRequestTimeout(10000);
// 在提交请求之前 测试连接是否可用
// configBuilder.setStaleConnectionCheckEnabled(true);
//cookie管理规范设定,此处有多种可以设置,按需要设置
// configBuilder.setCookieSpec(CookieSpecs.BROWSER_COMPATIBILITY);
RequestConfig requestConfig = configBuilder.build();
/**
* 池化定义
*/
// 默认该实现会为每个路由保持2个并行连接,总的数量上不超过20个连接
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
// 连接池的最大连接数
cm.setMaxTotal(200);
// 每个路由的最大连接数
cm.setDefaultMaxPerRoute(20);
// 某个路由的最大连接数
HttpHost localhost = new HttpHost("www.baidu.com", 80);
cm.setMaxPerRoute(new HttpRoute(localhost), 50);
/**
* 模拟浏览器cookie,设置到全局httpClient上,每次都发送都可以携带
*/
CookieStore cookieStore = new BasicCookieStore();
BasicClientCookie cookie = new BasicClientCookie("name", "value");
cookie.setDomain("www.grd.com");
cookie.setPath("/");
cookieStore.addCookie(cookie);
/**
* 配置定义
*/
// 创建可服用的httpClient
// CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(cm).build();
// httpClientBuilder配置构架器
HttpClientBuilder httpClientBuilder = HttpClients.custom();
// 设置默认请求配置
httpClientBuilder.setDefaultRequestConfig(requestConfig);
// 设置重试次数,此处注意,如果使用无参构造,重试次数为3。this(3, false);
httpClientBuilder.setRetryHandler(new DefaultHttpRequestRetryHandler(2, false));
// 设置默认cookie配置
// httpClientBuilder.setDefaultCookieStore(cookieStore);
// 设置默认https配置
// httpClientBuilder.setSSLSocketFactory(createSSLConn());
// httpClientBuilder.setDefaultConnectionConfig()
// 设置默认header配置
// httpClientBuilder.setDefaultHeaders();
// 获取httpClient
CloseableHttpClient httpClient = httpClientBuilder.build();
// 配置HttpClient的对应工厂HttpComponentsClientHttpRequestFactory
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient);
factory.setConnectTimeout(10000); // 链接超时
factory.setReadTimeout(10000);
factory.setConnectionRequestTimeout(1000);
return factory;
}
@Bean
private static SSLConnectionSocketFactory createSSLConn() {
SSLConnectionSocketFactory sslsf = null;
try
{
SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
return true;
}
}).build();
sslsf = new SSLConnectionSocketFactory(sslContext);
} catch (GeneralSecurityException e)
{
e.printStackTrace();
}
return sslsf;
}
/**
* 自定义的重试策略,需要的时候使用
*/
public void customHttpRequestRetryHandler(){
//请求失败时,进行请求重试
HttpRequestRetryHandler handler = new HttpRequestRetryHandler() {
@Override
public boolean retryRequest(IOException e, int i, HttpContext httpContext) {
if (i > 3){
//重试超过3次,放弃请求
logger.error("retry has more than 3 time, give up request");
return false;
}
if (e instanceof NoHttpResponseException){
//服务器没有响应,可能是服务器断开了连接,应该重试
logger.error("receive no response from server, retry");
return true;
}
if (e instanceof SSLHandshakeException){
// SSL握手异常
logger.error("SSL hand shake exception");
return false;
}
if (e instanceof InterruptedIOException){
//超时
logger.error("InterruptedIOException");
return false;
}
if (e instanceof UnknownHostException){
// 服务器不可达
logger.error("server host unknown");
return false;
}
if (e instanceof ConnectTimeoutException){
// 连接超时
logger.error("Connection Time out");
return false;
}
if (e instanceof SSLException){
logger.error("SSLException");
return false;
}
HttpClientContext context = HttpClientContext.adapt(httpContext);
HttpRequest request = context.getRequest();
if (!(request instanceof HttpEntityEnclosingRequest)){
//如果请求不是关闭连接的请求
return true;
}
return false;
}
};
}
}
如何使用 HttpClient 的全套功能#
很简单,可以直接通过 HttpComponentsClientHttpRequestFactory 拿到 HttpClient!直接绕过 RestTemplate,享用原生 HttpClient 的所有功能。
HttpComponentsClientHttpRequestFactory requestFactory = (HttpComponentsClientHttpRequestFactory) restTemplate.getRequestFactory();
HttpClient httpClient = requestFactory.getHttpClient();
ResponseEntity#
- ResponseEntity 继承与 HttpEntity,新增了 status 成员变量。使用 ResponseEntity 作为 controller 的返回值,我们可以方便地处理响应的 header,状态码以及 body。而通常使用的 @ResponseBody 注解,只能处理 body 部分。这也是为什么通常在下载场景中会使用 ResponseEntity,因为下载需要设置 header 里的 content-type 以及特殊的 status(比如 206)。
- ResponseBody 可以直接返回 Json 结果,ResponseEntity 不仅可以返回 json 结果,还可以定义返回的 HttpHeaders 和 HttpStatus。
- 温习一下 ResponseEntity 在 Spring mvc 中的实现:HttpEntityMethodProcessor 的 handleReturnValue 方法
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
// 获取ServletReuqest 与 ServletResponse
ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
Assert.isInstanceOf(HttpEntity.class, returnValue);
// 获取到responseEntity
HttpEntity<?> responseEntity = (HttpEntity<?>) returnValue;
// 将responseEntity的Header信息加入到ServletResponse
HttpHeaders outputHeaders = outputMessage.getHeaders();
HttpHeaders entityHeaders = responseEntity.getHeaders();
if (!entityHeaders.isEmpty()) {
entityHeaders.forEach((key, value) -> {
if (HttpHeaders.VARY.equals(key) && outputHeaders.containsKey(HttpHeaders.VARY)) {
List<String> values = getVaryRequestHeadersToAdd(outputHeaders, entityHeaders);
if (!values.isEmpty()) {
outputHeaders.setVary(values);
}
}
else {
outputHeaders.put(key, value);
}
});
}
// 如果是ResponseEntity,直接flush,返回数据。
if (responseEntity instanceof ResponseEntity) {
int returnStatus = ((ResponseEntity<?>) responseEntity).getStatusCodeValue();
outputMessage.getServletResponse().setStatus(returnStatus);
if (returnStatus == 200) {
if (SAFE_METHODS.contains(inputMessage.getMethod())
&& isResourceNotModified(inputMessage, outputMessage)) {
// Ensure headers are flushed, no body should be written.
outputMessage.flush();
// Skip call to converters, as they may update the body.
return;
}
}
else if (returnStatus / 100 == 3) {
String location = outputHeaders.getFirst("location");
if (location != null) {
saveFlashAttributes(mavContainer, webRequest, location);
}
}
}
// 转换body
writeWithMessageConverters(responseEntity.getBody(), returnType, inputMessage, outputMessage);
// Ensure headers are flushed even if no body was written.
outputMessage.flush();
- ResponseEntity 在 RestTemplate 中的应用,非常简单,ResponseEntity 只是用来做返回值的载体,封装了 header、httpStatus、body 信息,方便熟悉 Spring mvc 的人使用。原理代码如下:
private class ResponseEntityResponseExtractor<T> implements ResponseExtractor<ResponseEntity<T>> {
@Override
public ResponseEntity<T> extractData(ClientHttpResponse response) throws IOException {
// 此步骤将ClientHttpResponse中的InputStream类型的body,转化为T类型的Body。
T body = this.delegate.extractData(response);
// 将statusCode、HttpHeader、Body封装入ResponseEntity以供使用。
return ResponseEntity.status(response.getRawStatusCode()).headers(response.getHeaders()).body(body);
}
}
ParameterizedTypeReference#
RestTemplate 异常整理#
在请求发送时,返回不是 200 都会抛异常。
注意 HttpStatusCodeException、HttpClientErrorException、HttpServerErrorException,主要使用这三个。
RestTemplate 最关键的 exchange 方法 (不想学原理只想使用的直接看这里)#
使用 demo#
- http
public class Test {
public static void main(String[] args) {
// RestTemplate template = new RestTemplate();
// ResponseEntity<String> exchange = template.exchange("https://blog.csdn.net/qq_37099837/article/details/112461675", HttpMethod.GET, HttpEntity.EMPTY, String.class);
// System.out.println(exchange.getStatusCodeValue());
RestTemplate restTemplate = new RestTemplate();
// 加打印url与header的interceptor
restTemplate.setInterceptors(Collections.singletonList(printTemplateInterceptor()));
// 设置header
MultiValueMap<String,String> header = new LinkedMultiValueMap<>();
// 设置cookie
String cookies = "xxwww:xxxsss;";
header.add(HttpHeaders.COOKIE,cookies);
HttpEntity<MultiValueMap<String,String>> httpEntity = new HttpEntity<>(header);
// 设置路径参数
MultiValueMap<String,String> vars = new LinkedMultiValueMap<>();
vars.add("param1","grd");
// url
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString("http://www.test2.com/{param1}/test");
// 添加get参数
builder.queryParam("query","dfadsf");
ResponseEntity<String> responseEntity = restTemplate.exchange(builder.toUriString(), HttpMethod.GET, httpEntity, String.class,vars);
String res = responseEntity.getBody();
System.out.println(res);
}
public static ClientHttpRequestInterceptor printTemplateInterceptor(){
return new ClientHttpRequestInterceptor(){
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
// 通过请求获取请求中的header参数
HttpHeaders headers = request.getHeaders();
System.out.println("url:"+request.getURI().toString());
System.out.println("headers:"+headers);
return execution.execute(request, body);
}
};
}
}
- https
此处只讲针对 HttpClient 实现的配置:
HttpClientBuilder httpClientBuilder = HttpClients.custom();
httpClientBuilder.setSSLSocketFactory(createSSLConn());
// 获取httpClient
CloseableHttpClient build = httpClientBuilder.build();
// 配置HttpClient的对应工厂HttpComponentsClientHttpRequestFactory
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(build);
new RestTemplate(factory)
// Https配置
private static SSLConnectionSocketFactory createSSLConn() {
SSLConnectionSocketFactory sslsf = null;
try
{
SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
return true;
}
}).build();
sslsf = new SSLConnectionSocketFactory(sslContext);
} catch (GeneralSecurityException e)
{
e.printStackTrace();
}
return sslsf;
}
- 连接池配置:
直接看 ClientHttpRequestFactory 章节原理#
exchange 方法只是做了简单的封装,最终调用 doExecute。
一般就用这个方法,提供 url、HttpMethod、HttpEntity、responseType 即可最关键的 doExecute 解析。主要分为 4 个步骤:public <T> ResponseEntity<T> exchange(String url, HttpMethod method,HttpEntity<?> requestEntity, Class<T> responseType, Object... uriVariables) throws RestClientException { // 将HttpEntity、responseType封装到统一的HttpEntityRequestCallback中 RequestCallback requestCallback = httpEntityCallback(requestEntity, responseType); // 将responseType封装到ResponseEntityResponseExtractor中 ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType); return nonNull(execute(url, method, requestCallback, responseExtractor, uriVariables)); }
创建 ClientHttpRequest 对象 ->Java Class 转化为请求 body 输出流 -> 执行请求 -> 错误处理 -> 响应 body 转化为 Java Class分步解析:Java Class 与 body 的互转通用代码protected <T> T doExecute(URI url, @Nullable HttpMethod method, @Nullable RequestCallback requestCallback, @Nullable ResponseExtractor<T> responseExtractor) throws RestClientException { ...省略 try { // 创建ClientHttpRequest对象方法,实际会调用getRequestFactory().createRequest(url, method) ClientHttpRequest request = createRequest(url, method); if (requestCallback != null) { // 进行实际请求前对ClientHttpRequest对象的继续赋值补充,里面最关键的一步是通过遍历可用的HttpMessageConverter,通过请求body体的Class类型+http的content-type requestBodyClass来进行java类到body OutputStream的转换 requestCallback.doWithRequest(request); } // 实际执行请求,返回ClientHttpResponse response = request.execute(); // 通用错误处理,默认使用DefaultResponseErrorHandler handleResponse(url, method, response); // 响应中body体到java Class的转换,最后返回java Class,流程与上面的doWithRequest类似 return (responseExtractor != null ? responseExtractor.extractData(response) : null); } catch (IOException ex) { ... } }
// 遍历可用的HttpMessageConverter
...
// 通过Java Class与content-type判断是否执行此HttpMessageConverter转换
if(messageConverter.canWrite(requestBodyClass, requestContentType)) {
//执行转换,将转换后的结果放入httpRequest的body中
((HttpMessageConverter<Object>) messageConverter).write(requestBody, requestContentType,httpRequest);
}
其中,HttpMessageConverter在RestTemplate初始化的时候就已经加载了,举例加载jackson的原理:
```java
private static final boolean jackson2Present =
ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper",
RestTemplate.class.getClassLoader()) &&
ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator",
RestTemplate.class.getClassLoader());
public RestTemplate() {
...
if (jackson2XmlPresent) {
this.messageConverters.add(new MappingJackson2XmlHttpMessageConverter());
}
...
}
错误处理代码:
```java
ResponseErrorHandler errorHandler = getErrorHandler();
// 判断是否有错
boolean hasError = errorHandler.hasError(response);
// 有错调用handleError,默认DefaultResponseErrorHandler实现是抛异常。
if (hasError) {
errorHandler.handleError(url, method, response);
}
RestTemplate 责任链模式 ClientHttpRequestInterceptor#
原理很简单,RestTemplate 继承了 InterceptingHttpAccessor,RestTemplate extends InterceptingHttpAccessor implements HttpAccessor,其中 getRequestFactory () 方法被重写了:
// 如果interceptors不为空,则使用InterceptingClientHttpRequestFactory工厂。
if (!CollectionUtils.isEmpty(interceptors)) {
ClientHttpRequestFactory factory = this.interceptingRequestFactory;
if (factory == null) {
factory = new InterceptingClientHttpRequestFactory(super.getRequestFactory(), interceptors);
this.interceptingRequestFactory = factory;
}
return factory;
}
InterceptingClientHttpRequestFactory 工厂使用 InterceptingRequestExecution 执行 execute 方法,就是重写了上面我们提到的通过 ClientHttpRequest.execute () 获取 ClientHttpResponse 的过程。
在 InterceptingClientHttpRequestFactory 的 execute 方法中,先执行完所有的 interceptors,其中,interceptors 的结构是 List,不管使用 arrayList 或者 LinkedList,都是有序的,按顺序执行完所有的拦截器,最后执行实际请求代码。
上简单易懂的源码:
@Override
public ClientHttpResponse execute(HttpRequest request, byte[] body) throws IOException {
// 如果存在拦截器,则先执行完拦截器,每一次执行都可以更改Header、uri、以及body等信息。
if (this.iterator.hasNext()) {
ClientHttpRequestInterceptor nextInterceptor = this.iterator.next();
// 此处比较有意思,执行的是类似与dfs的递归调用
return nextInterceptor.intercept(request, body, this);
}
// 当dfs条件不满足,执行完拦截器后,进行实际的发送请求,得到最终的ClientHttpResponse
else {
.......
return delegate.execute();
}
}
如果此拦截器不是最后一个,想继续调用其它拦截器,则最后使用 return execution.execute (request, body); 继续递归调用其下一个拦截器。如果不想再递归了,作为最后一个拦截器,可以使用自行实现的方法 execute(如 ribben),直接 return ClientHttpResponse 即可。
为方便理解,再来一段简单的代码,自动加 header 实现微服务间的调用:
public ClientHttpRequestInterceptor restAuthorizationTemplateInterceptor(){
return new ClientHttpRequestInterceptor(){
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
// 通过请求获取请求中的header参数
HttpHeaders headers = request.getHeaders();
// 往header中设置自己需要的参数
headers.add("Authorization", getAuthorization());
headers.add("Content-Type", "application/json");
headers.add("Accept", MediaType.APPLICATION_JSON.toString());
return execution.execute(request, body);
}
};
}
@Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>();
// 拦截器按照添加的顺序执行
interceptors.add(restAuthorizationTemplateInterceptor());
restTemplate.setInterceptors(interceptors);
return restTemplate;
}
RestTemple 与 Springcloud#
@LoadBalanced 实现请求负载均衡#
通过拦截器在 RestTemplate 工作前进行负载均衡。该拦截器中注入了 LoadBalancerClient(负载均衡客户端)的实现。
// 使用了一个LoadBalancerInterceptor拦截器,实现对uri的重写
public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {
//Ribbon客户端
private LoadBalancerClient loadBalancer;
...
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) throws IOException {
....
//使用Ribbon执行调用
return (ClientHttpResponse)this.loadBalancer.execute(serviceName, this.requestFactory.createRequest(request, body, execution));
}
}
通过上面的代码我们看出请求最终交给 LoadBalanceClient 实例去执行,LoadBalanceClient 是 Ribbon 提供的负载均衡客户端,很多重要的逻辑都在这里处理。如下为 LoadBalanceClient 的类图:
RestTemple 与其它框架 (如 HttpClient) 是如何整合的?#
详细见本文的 ClientHttpRequestFactory 章节
关于 Http 的问题整理#
如何进行并发请求?#
内容过多,原理单独篇章实现吧。
仅做以下几点总结,不展示代码了,大家都会:
- 多线程实现的并发,在主线程维度看,属于异步阻塞,在工作线程维度看,属于同步阻塞。并发量一般,做一般的业务够了。
- 在底层看,实现非阻塞必须使用 epoll,或者 select、pool 实现。
异步可以使用开线程、协程实现。
顶级模型为协程模型,自动实现 epoll 事件调度(非阻塞)+ 开协程(异步)。
java 实现为基于 netty 的 Reactors 多线程模型,使用线程实现异步,epool 实现非阻塞。效率可与协程媲美。 - 同步阻塞:普通调用属于同步阻塞,开多线程处理在工作线程维度看依旧属于同步阻塞。
同步非阻塞、
异步阻塞:开多线程处理,在主线程维度看,属于异步阻塞。
异步非阻塞、 - 超大并发,想借力 netty,可使用 WebClient
当请求书并发过高,产生的连接池不够问题。#
查看 ClientHttpRequestFactory 的连接池配置章节,对连接数不够产生的原因与解决方案做了解释。
本作品采用《CC 协议》,转载必须注明作者和本文链接
感谢分享