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 协议》,转载必须注明作者和本文链接
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。