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到对象属性中。
    fromUriString方法
    示例:
// 使用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的连接池原理图:
    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即可
    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);
}
其中,HttpMessageConverterRestTemplate初始化的时候就已经加载了,举例加载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 协议》,转载必须注明作者和本文链接
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!