06、RestTemplate负载均衡原理

282 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 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