RestTemplate
RestTemplate 是 spring-web 模块提供的一个执行同步http请求的客户端,底层依赖的是 JDK HttpURLConnection, Apache HttpComponents 和 OkHttp3 等,在将请求提交给这些底层模块之前,提供了扩展点:通过ClientHttpRequestInterceptor接口的实现类对请求进行拦截处理。这篇文章是 Spring Cloud Loadbalancer 模块学习的前置文章。因为 Spring Cloud loadbalancer 是通过 ClientHttpRequestInterceptor 对 RestTemplate 进行负载均衡的。因此需要对 ClientHttpRequestInterceptor 有所了解。
- 需要注意的是,根据文档显示从Spring 5.0开始 RestTemplate 已经进入维护的阶段,目前主推的是org.springframework.web.reactive.client.WebClient,支持异步请求。
基本类
ClientHttpRequestInterceptor 接口是比较简单
public interface ClientHttpRequestInterceptor {
ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
throws IOException;
}
涉及的其他类如下,本质上是对HTTP协议的抽象。
// HttpRequest 代表了一个 http 请求体,包含了请求行(HttpMethod,URI),请求头
public interface HttpRequest extends HttpMessage {
@Nullable
default HttpMethod getMethod() {
return HttpMethod.resolve(getMethodValue());
}
String getMethodValue();
URI getURI();
}
public interface HttpMessage {
HttpHeaders getHeaders();
}
public class HttpHeaders implements MultiValueMapString, String>, Serializable {
// 省略
}
ClientHttpResponse 代表了http请求的响应,也是包含了状态码,响应头,响应体。
public interface ClientHttpResponse extends HttpInputMessage, Closeable {
HttpStatus getStatusCode() throws IOException;
int getRawStatusCode() throws IOException;
String getStatusText() throws IOException;
@Override
void close();
}
public interface HttpInputMessage extends HttpMessage {
InputStream getBody() throws IOException;
}
public interface ClientHttpRequestExecution {
ClientHttpResponse execute(HttpRequest request, byte[] body) throws IOException;
}
ClientHttpRequestExecution 主要是用于构建拦截器调用链,并通过调用栈的形式执行拦截器。
@FunctionalInterface
public interface ClientHttpRequestExecution {
ClientHttpResponse execute(HttpRequest request, byte[] body) throws IOException;
}
实现类如下:
class InterceptingClientHttpRequest extends AbstractBufferingClientHttpRequest {
private final ListClientHttpRequestInterceptor> interceptors;
private final ClientHttpRequestFactory requestFactory;
private class InterceptingRequestExecution implements ClientHttpRequestExecution {
private final IteratorClientHttpRequestInterceptor> iterator;
public InterceptingRequestExecution() {
this.iterator = interceptors.iterator();
}
@Override
public ClientHttpResponse execute(HttpRequest request, byte[] body) throws IOException {
if (this.iterator.hasNext()) {
// 执行调用链
ClientHttpRequestInterceptor nextInterceptor = this.iterator.next();
return nextInterceptor.intercept(request, body, this);
}// 调用链结束,回到主流程,获取底层的 ClientHttpRequest,并执行。
else {
HttpMethod method = request.getMethod();
Assert.state(method != null, "No standard HTTP method");
ClientHttpRequest delegate = requestFactory.createRequest(request.getURI(), method);
request.getHeaders().forEach((key, value) -> delegate.getHeaders().addAll(key, value));
if (body.length > 0) {
if (delegate instanceof StreamingHttpOutputMessage) {
StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) delegate;
streamingOutputMessage.setBody(outputStream -> StreamUtils.copy(body, outputStream));
}
else {
StreamUtils.copy(body, delegate.getBody());
}
}
return delegate.execute();
}
}
}
// 省略..
}
拦截器应用
拦截器的应用还是比较简单的,只需往 RestTemplate 实例添加拦截器即可。
public class InterceptorTest {
public static void main(String[] args) {
ClientHttpRequestInterceptor i1 = new ClientHttpRequestInterceptor() {
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
System.out.println("拦截器1开始。。。");
ClientHttpResponse execute = execution.execute(request, body);
System.out.println("拦截器1结束。。。");
return execute;
}
};
ClientHttpRequestInterceptor i2 = new ClientHttpRequestInterceptor() {
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
System.out.println("拦截器2开始。。。");
ClientHttpResponse execute = execution.execute(request, body);
System.out.println("拦截器2结束。。。");
return execute;
}
};
RestTemplate restTemplate = new RestTemplate();
restTemplate.getInterceptors().add(i1);
restTemplate.getInterceptors().add(i2);
String forObject = restTemplate.getForObject("http://www.baidu.com", String.class);
System.out.println(forObject);
}
}
执行以上代码,输出如下:
- 拦截器1开始。。。
- 拦截器2开始。。。
- 拦截器2结束。。。
- 拦截器1结束。。。
我们作出一点改变,通过拦截器来实现负载均衡。
public class InterceptorTest {
static class Mywraper extends HttpRequestWrapper{
private String url;
public Mywraper(HttpRequest request,String url) {
super(request);
this.url = url;
}
@Override
public URI getURI() {
try {
return new URI(url);
} catch (URISyntaxException e) {
}
return null;
}
}
public static void main(String[] args) {
String[] loabancerhost = {"https://www.baidu.com", "https://www.sina.com.cn"};
AtomicInteger times = new AtomicInteger(0);
ClientHttpRequestInterceptor i1 = new ClientHttpRequestInterceptor() {
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
int length = loabancerhost.length;
int i = times.getAndIncrement() % length;
//因为 HttpRequest 和 URI 不提供修改功能,因此需要借助 HttpRequestWrapper 对request进行包装
Mywraper mywraper = new Mywraper(request, loabancerhost[i]);
ClientHttpResponse execute = execution.execute(mywraper, body);
return execute;
}
};
ClientHttpRequestInterceptor i2 = new ClientHttpRequestInterceptor() {
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
System.out.println("第"+times+"次请求的host为: "+request.getURI());
ClientHttpResponse execute = execution.execute(request, body);
return execute;
}
};
RestTemplate restTemplate = new RestTemplate();
restTemplate.getInterceptors().add(i1);
restTemplate.getInterceptors().add(i2);
for (int i = 10; i > 0; i--) {
restTemplate.getForObject("http://www.baidu.com", String.class);
}
}
}
- 第1次请求的host为: https://www.baidu.com
- 第2次请求的host为: https://www.sina.com.cn
- 第3次请求的host为: https://www.baidu.com
- 第4次请求的host为: https://www.sina.com.cn
- 第5次请求的host为: https://www.baidu.com
- 第6次请求的host为: https://www.sina.com.cn
- 第7次请求的host为: https://www.baidu.com
- 第8次请求的host为: https://www.sina.com.cn
- 第9次请求的host为: https://www.baidu.com
- 第10次请求的host为: https://www.sina.com.cn
至此,如何通过 InterceptingClientHttpRequest 进行 RestTemplate 负载均衡介绍完毕,本质上原理还是很简单的。只需要通过拦截器修改URI即可。