RestTemplate负载均衡原理
1、@LoadBalanced注解概述
- RestTemplate本是spring-web项目中的一个REST客户端访问类
- RestTemplate遵循REST的设计原则,提供简单的API让调用去访问HTTP服务器
- RestTemplate本身不具备负载均衡的功能
- RestTemplate与SpringCloud没有关系
- 回顾之前的一段代码
@LoadBalanced
@Bean
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
- 但是,为什么加入@LoadBalanced注解后,一个RestTemplate实例就具备负载均衡功能?
- 实际上,这要得益于RestTemplate的拦截器的功能
- 在Spring容器启动时,会为这些修饰过的RestTemplate添加拦截器,拦截其中使用了LoadBalancerClient来处理请求,LoadBalancerClient本来就是Spring封装的负载均衡客户端,通过这样间接处理,使得RestTemplate拥有了负载均衡的功能
- 本小节将模仿拦截器机制,带领打击实现一个简单的RestTemplate,让大家更加了解@LoadBalanced以及RestTemplate的原理
- 案例只依赖了spring-boot-starter-web模块
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>1.5.4.RELEASE</version>
</dependency
2、模拟@LoadBanlanced
2.1、编写自定义注解
- 先模仿@LoadBalanced注解,编写一个自定义注解
package com.atm.cloud
import java.lang.annotation.ElementType
import java.lang.annotation.Retention
import java.lang.annotation.RetentionPolicy
import java.lang.annotation.Target
import org.springframework.beans.factory.annotation.Qualifier
@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface MyLoadBalanced {
}
- MyLoadBalanced注解中使用了@Qualifier限定注解
- 接下来编写自定义的拦截器
2.2、编写自定义请求类
package com.atm.cloud;
import java.net.URI;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpRequest;
public class MyRequest implements HttpRequest {
private HttpRequest sourceRequest;
public MyRequest(HttpRequest souHttpRequest) {
this.sourceRequest = souHttpRequest;
}
@Override
public HttpHeaders getHeaders() {
return sourceRequest.getHeaders();
}
@Override
public HttpMethod getMethod() {
return sourceRequest.getMethod();
}
/**
* 将URI转化
*/
@Override
public URI getURI() {
try {
String oldUri = sourceRequest.getURI().toString();
URI newUri = new URI("http://localhost:8080/hello");
return newUri;
} catch (Exception e) {
e.printStackTrace();
}
return sourceRequest.getURI();
}
}
2.3、编写控制器
package com.atm.cloud
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.ResponseBody
import org.springframework.web.bind.annotation.RestController
import org.springframework.web.client.RestTemplate
@RestController
@Configuration
public class MyController {
@Bean
@MyLoadBalanced
RestTemplate restTemplateA(){
return new RestTemplate()
}
@GetMapping("/router")
@ResponseBody
public String router() {
RestTemplate restTemplate = restTemplateA()
// 根据应用名称调用服务,这个 URI 会被拦截器所置换
String json = restTemplate.getForObject(
"http://atm-eureka-ribbon-provider/router", String.class)
return json
}
@GetMapping("/hello")
@ResponseBody
public String hello() {
return "Hello World"
}
}
2.4、编写自定义拦截器
- 在自定义拦截器MyInterceptor中,实现了intercept方法
- 该方法中将原来HttpRequest对象,转换为我们自定义的MyRequest
package com.atm.cloud;
import java.io.IOException;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
public class MyInterceptor implements ClientHttpRequestInterceptor {
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body,
ClientHttpRequestExecution execution) throws IOException {
System.out.println(" --->>> 这是自定义拦截器 <<<--- ");
System.out.println(" --->>> 原来的URI:" + request.getURI());
MyRequest newRequest =new MyRequest(request);
System.out.println(" --->>> 拦截后新的URI:"+newRequest.getURI());
return execution.execute(newRequest, body);
}
}
- MyRequest类中,将原来请求的URI进行该写
- 只要使用了这个对象,所有的请求都会被转发到http://localhost:8080/hello
- SpringCloud在对RestTemplate进行拦截的时候,也做了同样的事情,只不过没有像我们这样固定了URI,而是对“源请求”进行了更加灵活的处理
2.5、编写自定义配置
- 编写一个Spirng的配置类,在初始化的Bean中为容器中的RestTemplate实例设置自定义拦截器
package com.atm.cloud;
import java.util.Collections;
import java.util.List;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.web.client.RestTemplate;
@Configuration
public class MyConfig {
@Autowired(required = false)
@MyLoadBalanced
private List<RestTemplate> restTemplates = Collections.emptyList();
@Bean
public SmartInitializingSingleton lbInitializingSingleton() {
return new SmartInitializingSingleton() {
@Override
public void afterSingletonsInstantiated() {
for (RestTemplate restTemplate : restTemplates) {
List<ClientHttpRequestInterceptor> clientHttpRequestInterceptors = restTemplate
.getInterceptors();
clientHttpRequestInterceptors.add(new MyInterceptor());
restTemplate.setInterceptors(clientHttpRequestInterceptors);
}
}
};
}
}
- 配置类中,定义了RestTemplate实例的集合,并且使用@MyLoadBalanced以及@Autowired注解进行修饰
- @MyLoadBalanced中含有@Qualifier注解,简单来说,就是Spring容器中,使用@MyLoadBalanced修饰的RestTemplate实例,将会被加入到配置类的RestTemplate集合中
- 在容器初始化时,会调用myLoadBalancedRestTemplateInitializer方法来创建Bean,该Bean在初始化完成后,会遍历ResstTemplate集合并为他们设置“自定义拦截器”
2.6、测试