1.概述
使用注解就像是在代码中加入了一段魔法,让我们轻而易举的就实现了至关重要的功能。
就像@LoadBalanced
一样,将该注解使用在RestTemplate
的Bean上,就可以实现负载均衡,就像下面这段代码:
@Configuration
public class CustomConfiguration {
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
RestTemplate是Spring提供的一个简单的Rest模板客户端,用来进行API的调用。在不需要负载均衡的时候,可能我们的代码会是这个样子。
public class LoadBalanceTest {
public void test1() {
RestTemplate restTemplate = new RestTemplate();
String result = restTemplate.getForObject("https://www.baidu.com", String.class);
System.out.println(result);
}
public void test2() {
RestTemplate restTemplate = new RestTemplate();
String result = restTemplate.getForObject("http://localhost:8080/test", String.class);
System.out.println(result);
}
}
我们只是添加了一个注解,就实现了负载均衡的功能,得益于Spring最底层实现逻辑的封装,才让我们在开发中使用起来如此的得心应手。此时我们的代码就会变成这个样子,使用http://provider/test
,不会直接使用http://ip:port/test
的形式,而是使用服务名称,根据服务名称去注册中心获取实际的服务信息,最终再转换为要请求的地址。
@RestController
public class LoadBalanceController {
@Autowired
private RestTemplate restTemplate;
public void test() {
// provider代表的是服务提供者的spring.application.name的值
String result = restTemplate.getForObject("http://provider/test", String.class);
System.out.println(result);
}
}
2.@LoadBalanced注解分析
下面我们来一步一步揭开@LoadBalanced
的庐山真面目。
/**
* Annotation to mark a RestTemplate or WebClient bean to be configured to use a
* LoadBalancerClient.
* @author Spencer Gibb
*/
@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public @interface LoadBalanced {
}
其注释中写到,该注解是用来标记要使用LoadBalancerClient
的RestTemplate
或WebClient
的bean,从而实现负载均衡。
在上述源码中@Qualifier
还历历在目,我们知道@Qualifier
经常与@Autowired
注解成对出现,原因是@Autowired
是按bean的类型来装配的,如果SpringIOC容器中,存在两个类型相同的bean时,将无法进行自动装配,这个时候可以结合@Qualifier
根据bean的名称来进行装配,到这里就可以明白,@Qualifier
起着限定的作用。
因此,@LoadBalanced
继承了该特性,这里先埋下一个猜测,SpringCloud的底层封装也会用到该注解,用于对特定的RestTemplate进行处理。
前面注释中提到了LoadBalancerClient
,我们先来看看这个接口。
public interface LoadBalancerClient extends ServiceInstanceChooser {
/**
* 根据指定服务名称来执行请求
*/
<T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException;
/**
* 根据指定服务名称以及指定的服务实例来执行请求
*/
<T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException;
/**
* 获得真实的URI,将http://provider/test转换为http://localhost:8080/test
*/
URI reconstructURI(ServiceInstance instance, URI original);
}
继续追踪,我们找到了LoadBalancerAutoConfiguration
,这就是LoadBalancer的自动配置类。以下为截取的部分源码:
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerProperties.class)
public class LoadBalancerAutoConfiguration {
// 果不其然,@LoadBalanced又出现了,这里的目的是为了装配添加了@LoadBalanced注解的RestTemplate的bean
@LoadBalanced
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList();
@Autowired(required = false)
private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList();
@Bean
public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
return () -> restTemplateCustomizers.ifAvailable(customizers -> {
for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
for (RestTemplateCustomizer customizer : customizers) {
customizer.customize(restTemplate);
}
}
});
}
@Bean
@ConditionalOnMissingBean
public LoadBalancerRequestFactory loadBalancerRequestFactory(LoadBalancerClient loadBalancerClient) {
return new LoadBalancerRequestFactory(loadBalancerClient, this.transformers);
}
@Configuration(proxyBeanMethods = false)
@Conditional(RetryMissingOrDisabledCondition.class)
static class LoadBalancerInterceptorConfig {
@Bean
public LoadBalancerInterceptor loadBalancerInterceptor(LoadBalancerClient loadBalancerClient,
LoadBalancerRequestFactory requestFactory) {
return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
}
@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(final LoadBalancerInterceptor loadBalancerInterceptor) {
return restTemplate -> {
List<ClientHttpRequestInterceptor> list = new ArrayList<>(restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
};
}
}
}
果不其然,@LoadBalanced
出现在了SpringCloud的底层代码中,主要目的是起一个限定的作用,这里只会装配添加了@LoadBalanced
的RestTemplate。
到这里,标注有@LoadBalanced
注解的RestTemplate的bean已经装配进来,毫无疑问,接下来就是对RestTemplate的请求进行拦截。自然需要为每个RestTemplate设置拦截器。
3.为RestTemplate设置拦截器
那么为什么可以为RestTemplate设置拦截器呢?我们来看看RestTemplate的类图。
从图中可以看出InterceptingHttpAccessor
是一个和拦截器有关系类,我们来一探究竟。
是的,在InterceptingHttpAccessor中为我们存储了拦截器列表。
我们在回到LoadBalancerAutoConfiguration中,看看自动配置的LoadBalancerInterceptor
1、创建拦截器
@Configuration(proxyBeanMethods = false)
@Conditional(RetryMissingOrDisabledCondition.class)
static class LoadBalancerInterceptorConfig {
// 创建负载均衡拦截器
@Bean
public LoadBalancerInterceptor loadBalancerInterceptor(LoadBalancerClient loadBalancerClient,
LoadBalancerRequestFactory requestFactory) {
return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
}
}
2、创建RestTemplateCustomizer
@Configuration(proxyBeanMethods = false)
@Conditional(RetryMissingOrDisabledCondition.class)
static class LoadBalancerInterceptorConfig {
// 创建RestTemplateCustomizer
@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(final LoadBalancerInterceptor loadBalancerInterceptor) {
// 利用lambda表达式来创建RestTemplateCustomizer
return restTemplate -> {
List<ClientHttpRequestInterceptor> list = new ArrayList<>(restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
};
}
}
RestTemplateCustomizer是一个函数式接口。
public interface RestTemplateCustomizer {
void customize(RestTemplate restTemplate);
}
3、设置拦截器
获取到所有的RestTemplateCustomizer,并调用其customize()方法,这时会回调步骤2的定义的逻辑。
@Bean
public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
return () -> restTemplateCustomizers.ifAvailable(customizers -> {
for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
for (RestTemplateCustomizer customizer : customizers) {
customizer.customize(restTemplate);
}
}
});
}
我们来看这段回调的具体逻辑,指向了customize()方法。
这里用到了SmartInitializingSingleton
,不得不提到bean的初始化过程。
先来看看SmartInitializingSingleton
。
public interface SmartInitializingSingleton {
void afterSingletonsInstantiated();
}
再看bean的初始化过程,最后最终源码可得知其调用过程:
refresh()-->finishBeanFactoryInitialization()-->preInstantiateSingletons()
最终完成了对SmartInitializingSingleton
中afterSingletonsInstantiated()
的调用,从而成功为RestTemplate
设置了LoadBalancerInterceptor
。
到目前为止,我们理清楚了对RestTemplate设置拦截器的原理,接下来就是分析如何对RestTemplate进行的请求进行拦截的。
4.RestTemplate调用过程
这里以RestTemplate的get请求为例进行分析:
public void test() {
// provider代表的是服务提供者的spring.application.name的值
String result = restTemplate.getForObject("http://provider/test", String.class);
System.out.println(result);
}
4.1 创建InterceptingClientHttpRequest
1)RestTemplate.doExecute()
2)调用InterceptingHttpAccessor.getRequestFactory(),获取InterceptingClientHttpRequestFactory
3)调用InterceptingClientHttpRequestFactory.createRequest(),获取InterceptingClientHttpRequest
4.2 执行拦截
1)调用InterceptingClientHttpRequest.executeInternal()
2)将InterceptingClientHttpRequest封装为ServiceRequestWrapper,然后使用LoadBalancerClient执行请求
这里在执行LoadBalancerClient的execute方法时,首先要构造一个LoadBalancerRequest,跟进创建方式时,发现使用lambda表达式进行构建,在调用其方法时,就会进行回调。
public interface LoadBalancerRequest<T> {
T apply(ServiceInstance instance) throws Exception;
}
构造一个LoadBalancerRequest
4.3 发送请求
1)获取ServiceInstance的真实host、port
当执行完所有的拦截器方法后,就来到了获取真实请求地址的host、port环节
前面我们知道InterceptingClientHttpRequest被封装到了ServiceRequestWrapper,因此来到了ServiceRequestWrapper.getURI()
,实际上使用的LoadBalancerClient.reconstructURI()
,前面我们提到过,这个方法就是获取真实URI。
public class ServiceRequestWrapper extends HttpRequestWrapper {
private final ServiceInstance instance;
private final LoadBalancerClient loadBalancer;
public ServiceRequestWrapper(HttpRequest request, ServiceInstance instance, LoadBalancerClient loadBalancer) {
super(request);
this.instance = instance;
this.loadBalancer = loadBalancer;
}
@Override
public URI getURI() {
//
URI uri = this.loadBalancer.reconstructURI(this.instance, getRequest().getURI());
return uri;
}
}
5.小结
1、在创建RestTemplate的bean时添加@LoadBalanced注解
2、LoadBalancerAutoConfiguration自动配置筛选出添加@LoadBalanced注解的RestTemplate
3、为RestTemplate设置LoadBalancerInterceptor,目的是使用LoadBalancerClient来发起请求
4、请求过程中,根据负载均衡策略,调用LoadBalancerClient.choose()方法获取最终ServiceInstance
5、根据ServiceInstance获取真实host、port,发起最后请求