持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第6天,点击查看活动详情
06、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.编写自定义注解以及拦截器
自定义注解:
@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface MyLoadBalanced {
}
自定义拦截器:
public class MyInterceptor implements ClientHttpRequestInterceptor {
public ClientHttpResponse intercept(HttpRequest request, byte[] body,
ClientHttpRequestExecution execution) throws IOException {
System.out.println("============= 这是自定义拦截器实现");
System.out.println(" 原来的URI:" + request.getURI());
// 换成新的请求对象(更换URI)
MyHttpRequest newRequest = new MyHttpRequest(request);
System.out.println(" 拦截后新的URI:" + newRequest.getURI());
return execution.execute(newRequest, body);
}
}
自定义请求类:
public class MyHttpRequest implements HttpRequest {
private HttpRequest sourceRequest;
public MyHttpRequest(HttpRequest sourceRequest) {
this.sourceRequest = sourceRequest;
}
public HttpHeaders getHeaders() {
return sourceRequest.getHeaders();
}
public HttpMethod getMethod() {
return sourceRequest.getMethod();
}
@Override
public String getMethodValue() {
return sourceRequest.getMethodValue();
}
/**
* 将URI转换
*/
public URI getURI() {
try {
URI newUri = new URI("http://localhost:9000/hello");
return newUri;
} catch (Exception e) {
e.printStackTrace();
}
return sourceRequest.getURI();
}
}
- MyRequest类中,将原来请求的URI进行该写
- 只要使用了这个对象,所有的请求都会被转发到
http://localhost:9000/hello - SpringCloud在对RestTemplate进行拦截的时候,也做了同样的事情,只不过没有像我们这样固定了URI,而是对“源请求”进行了更加灵活的处理**
3.使用自定义拦截器以及注解
配置类中,定义了RestTemplate实例的集合,并且使用@MyLoadBalanced以及@Autowired注解进行修饰 @MyLoadBalanced中含有@Qualifier注解,简单来说,就是Spring容器中,使用@MyLoadBalanced修饰的RestTemplate实例,将会被加入到配置类的RestTemplate集合中 在容器初始化时,会调用myLoadBalancedRestTemplateInitializer方法来创建Bean,该Bean在初始化完成后,会遍历ResstTemplate集合并为他们设置“自定义拦截器”
为什么被@MyLoadBalanced修饰的RestTemplate实例,将会被加入到配置类的RestTemplate集合中: 因为@MyLoadBalanced中含有@Qualifier注解。而@Qualifier就是起一个标识作用。
当spring容器中有多个相同类型的bean的时候,可以通过@Qualifier来进行区分,以便在注入的时候明确表明你要注入具体的哪个bean,消除歧义。 详细解释请见: blog.csdn.net/xiao_jun_08…
这里注入的RestTemplate集合,是容器中已经存在的 RestTemplate
@Configuration
public class MyAutoConfiguration {
@Autowired(required=false)
@MyLoadBalanced
private List<RestTemplate> myTemplates = Collections.emptyList();
@Bean
public SmartInitializingSingleton myLoadBalancedRestTemplateInitializer() {
System.out.println("==== 这个Bean将在容器初始化时创建 =====");
return new SmartInitializingSingleton() {
public void afterSingletonsInstantiated() {
for(RestTemplate tpl : myTemplates) {
// 创建一个自定义的拦截器实例
MyInterceptor mi = new MyInterceptor();
// 获取RestTemplate原来的拦截器
List list = new ArrayList(tpl.getInterceptors());
// 添加到拦截器集合
list.add(mi);
// 将新的拦截器集合设置到RestTemplate实例
tpl.setInterceptors(list);
}
}
};
}
}
在控制器中使用 RestTemplate
@RestController
@Configuration
public class RestTemplateController {
@Bean
@MyLoadBalanced
public RestTemplate getMyRestTemplate() {
return new RestTemplate();
}
/**
* 浏览器访问的请求
*/
@RequestMapping(value = "/test", method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
public String router() {
RestTemplate restTpl = getMyRestTemplate();
// 根据名称来调用服务,这个URI会被拦截器所置换
String json = restTpl.getForObject("http://provider-server/hello", String.class);
return json;
}
/**
* 最终的请求都会转到这个服务
*/
@RequestMapping(value = "/hello", method = RequestMethod.GET)
@ResponseBody
public String hello() {
return "Hello World -- invoker";
}
}
访问:http://localhost:9000/test
结果:
原来的URI:http://provider-server/hello
拦截后新的URI:http://localhost:9000/hello