文章目录
- 前言
- 关于Http的问题整理
前言
目的是在需要的时候以最快的、无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
当在返回bean中包含泛型变量时,如果想指定具体类型,需要使用ParameterizedTypeReference,举例如下:
// 常用的接口响应实体,其中的result变量是泛型
public class ResponseBean<T>
{
/**
* 返回码 0成功,其他失败
*/
private String code;
/**
* 描述信息
*/
private String message;
/**
* 返回结果
*/
private T result;
}
// 实际的泛型实体是CalculateBean
public class CalculateBean
{
/**
* 总价格
*/
String totalPrice;
/**
* 各个产品价格
*/
List<ProductPriceBean> productPriceList;
/**
* 新增产商品列表
*/
List<ProductItemBean> productItemList;
}
// 这时可以使用ParameterizedTypeReference将ResponseBean中的泛型指明
ParameterizedTypeReference<ResponseBean<CalculateBean>> typeRef = new ParameterizedTypeReference<ResponseBean<CalculateBean>>() {};
exchange("url","GET",HttpEntity.EMPTY,typeRef);
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);
// 添加get参数,方式二,不推荐
//Map map = new HashMap();
//map.put("param1","value1");
//ResponseEntity<String> responseEntity = restTemplate.exchange(url?param1={param1}, HttpMethod.GET, httpEntity, String.class,map);
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即可
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));
}
最关键的doExecute解析。主要分为4个步骤:
创建ClientHttpRequest对象->Java Class转化为请求body输出流->执行请求->错误处理->响应body转化为Java Class
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) {
...
}
}
分步解析:Java Class 与 body 的互转通用代码
// 遍历可用的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的连接池配置章节,对连接数不够产生的原因与解决方案做了解释。