RestTemplate总结

前言

目的是在需要的时候以最快的、无bug、可承载大并发大数据下的标准使用。
可供日常学习、编写脚本、开发时使用。
希望阅读本文后,不会再被任何http、接口请求类的问题吓到!有问题找blog,解决不了请留言,会及时补充,永久更新!。

整体架构图

在这里插入图片描述

基本使用与原理

RestTemplate主要方法

http方法RestTemplate方法
GETgetForObject(String url, Class responseType,Object… urlVariables)
POSTpostForObject(String url, Object request,Class responseType,Object… urlVariables)
PUTput(String url, Object request,Object… urlVariables)
DELETEdelete(String url,Object… urlVariables)
HEADheadForHeaders(String url,Map<String,?> urlVariables)
OPTIONSoptionsForAllow(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

当在返回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);
}
其中,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的连接池配置章节,对连接数不够产生的原因与解决方案做了解释。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值