使用注解就像是在代码中加入了一段魔法,让我们轻而易举的就实现了至关重要的功能。
RestTemplate是Spring提供的一个简单的Rest模板客户端,用来进行API的调用。
我们可以在单体web应用中使用RestTemplate来进行对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);
}
}
如果要在微服务应用中使用RestTemplate,并达到负载均衡的效果应该怎么做呢?我们知道,在微服务中,一般同一个服务实例不只一个,那么就涉及到请求过程中服务实例的选择问题。答案是使用@LoadBalanced
注解,将该注解加在RestTemplate
的Bean上,就可以实现负载均衡,就像下面这段代码:
@Configuration
public class CustomConfiguration {
@Bean
@LoadBalanced // 开启负载均衡能力
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
我们只是添加了一个注解,就实现了负载均衡的功能,得益于SpringCloud对底层实现逻辑的封装,才让我们在开发中使用起来如此的得心应手。此时我们的代码就会变成这个样子,使用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);
}
}
下面我们来一步一步揭开@LoadBalanced
的庐山真面目。
/**
* Annotation to mark a RestTemplate or WebClient bean to be configured to use a
* LoadBalancerClient.
*
*/
@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public @interface LoadBalanced {
}
其注释中写到,该注解是用来标记要使用LoadBalancerClient
的RestTemplate
或WebClient
的bean,意思就是最终底层使用的是LoadBalancerClient来进行调用,这个结论会在我们后续的分析中一步步清晰明了。
@Qualifier
起着限定的作用,可以筛选出符合条件的bean。
我们首先关注@Qualifier
注解,我们看到接口LoadBalanced
的定义上,添加了@Qualifier
注解,所以我们再来回顾一下。
当SpringIOC容器中有多个同类型的bean时,在使用@Autowired进行装配时,就无法完成自动装配,原因是@Autowired
是按bean的类型来装配的,可想而知,Spring也不知道我们到底要装配哪个bean,于是乎,@Qualifier
的出现就是为了解决这个问题。这个时候可以结合@Qualifier
,根据bean的名称来进行装配,到这里就可以明白,@Qualifier
起着限定的作用。
1、添加同类型不同名称的多个bean
@Configuration
public class CustomConfiguration {
@Bean
public RestTemplate restTemplate1() {
return new RestTemplate();
}
@Bean
public RestTemplate restTemplate2() {
return new RestTemplate();
}
}
2、注入指定名称的bean
@RestController
public class LoadBalanceController {
@Autowired
@Qualifier("restTemplate2")
private RestTemplate restTemplate;
}
因此,@LoadBalanced
继承了该特性,这里先埋下一个猜测,SpringCloud的底层封装也会用到该注解,用于对特定的RestTemplate进行处理,不然怎么对RestTemplate进行处理呢,是吧。
LoadBalancerClient:见名知意,这是一个负载均衡的客户端。
前面得知,在负载均衡模式下,SpringCloud会使用LoadBalancerClient
对RestTemplate发起的请求进行处理,我们先来看看这个接口。
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);
}
结论:根据源码,可以分析出,
LoadBalancerClient
可以向特定的服务实例发送请求。
并且LoadBalancerClient
继承自ServiceInstanceChooser
,见名知意ServiceInstanceChooser
应该是用来帮我们从同一个服务的多个服务实例中根据负载均衡策略选择出所需的服务实例。按照惯例,我们来看看其源码:
public interface ServiceInstanceChooser {
// 根据指定的服务名称选择服务实例
ServiceInstance choose(String serviceId);
// 根据指定的服务名称以及请求选择服务实例
<T> ServiceInstance choose(String serviceId, Request<T> request);
}
结论:ServiceInstanceChooser提供的方法,可以帮助我们根据负载均衡算法选择出实际要调用的服务实例。
继续追踪,我们找到了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,并装配到restTemplates中。
到这里,标注有@LoadBalanced
注解的RestTemplate的bean已经装配进来,那么怎么对RestTemplate发起的请求进行处理呢?SpringCloud采用拦截器的方式。
那么为什么可以为RestTemplate设置拦截器呢?我们来看看RestTemplate的类图。
从图中可以看出InterceptingHttpAccessor
是一个和拦截器有关系类,我们来一探究竟。
结论:在InterceptingHttpAccessor中为我们存储了拦截器列表,因此我们可以为RestTemplate设置拦截器。
到此,我们已经知道了拦截器的存放位置,接下来就是如何创建并设置拦截器了。
我们再回到LoadBalancerAutoConfiguration
中,看看自动配置的LoadBalancerInterceptor
。
@Configuration(proxyBeanMethods = false)
@Conditional(RetryMissingOrDisabledCondition.class)
static class LoadBalancerInterceptorConfig {
// 创建负载均衡拦截器
@Bean
public LoadBalancerInterceptor loadBalancerInterceptor(LoadBalancerClient loadBalancerClient,
LoadBalancerRequestFactory requestFactory) {
return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
}
}
public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {
private LoadBalancerClient loadBalancer;
private LoadBalancerRequestFactory requestFactory;
public LoadBalancerInterceptor(LoadBalancerClient loadBalancer, LoadBalancerRequestFactory requestFactory) {
this.loadBalancer = loadBalancer;
this.requestFactory = requestFactory;
}
public LoadBalancerInterceptor(LoadBalancerClient loadBalancer) {
// for backwards compatibility
this(loadBalancer, new LoadBalancerRequestFactory(loadBalancer));
}
// 拦截方法,使用LoadBalancerClient来执行请求
@Override
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
final ClientHttpRequestExecution execution) throws IOException {
final URI originalUri = request.getURI();
String serviceName = originalUri.getHost();
Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);
return this.loadBalancer.execute(serviceName, this.requestFactory.createRequest(request, body, execution));
}
}
结论:将LoadBalancerClient以及LoadBalancerRequestFactory封装到LoadBalancerInterceptor中。并在intercept()方法中,使用LoadBalancerClient执行请求
第一步,我们只是创建了拦截器,还没有将拦截器设置到RestTemplate
中。我们先看看RestTemplateCustomizer
,后续会使用到。
RestTemplateCustomizer是一个函数式接口。
public interface RestTemplateCustomizer {
void customize(RestTemplate restTemplate);
}
@Configuration(proxyBeanMethods = false)
@Conditional(RetryMissingOrDisabledCondition.class)
static class LoadBalancerInterceptorConfig {
// 创建RestTemplateCustomizer
@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(final LoadBalancerInterceptor loadBalancerInterceptor) {
// 利用lambda表达式来创建RestTemplateCustomizer,后续执行customize(),便会直接执行这段逻辑。
return restTemplate -> {
List<ClientHttpRequestInterceptor> list = new ArrayList<>(restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
};
}
}
第二步,已经对RestTemplateCustomizer
进行了创建,接下来就是获取到所有的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
,为什么注入一个SmartInitializingSingleton
bean,就能帮助我们为RestTemplate设置拦截器呢?这就不得不提到bean的初始化过程。
先来看看SmartInitializingSingleton
。
public interface SmartInitializingSingleton {
void afterSingletonsInstantiated();
}
再看bean的初始化过程,最后最终源码可得知其调用过程:
refresh()-->finishBeanFactoryInitialization()-->preInstantiateSingletons()
最终完成了对SmartInitializingSingleton
中afterSingletonsInstantiated()
的调用,从而成功为RestTemplate
设置了LoadBalancerInterceptor
。
到目前为止,我们理清楚了对RestTemplate设置拦截器的原理,接下来就是分析如何对RestTemplate进行的请求进行拦截的。
这里以RestTemplate的get请求为例进行分析:
public void test() {
// provider代表的是服务提供者的spring.application.name的值
String result = restTemplate.getForObject("http://provider/test", String.class);
System.out.println(result);
}
1)RestTemplate.doExecute()
2)封装ClientHttpRequest
3)使用ClientHttpRequest执行请求
封装ClientHttpRequest的内部实现:
4)调用InterceptingHttpAccessor.getRequestFactory(),获取InterceptingClientHttpRequestFactory
5)调用InterceptingClientHttpRequestFactory.createRequest(),获取InterceptingClientHttpRequest
1)调用InterceptingClientHttpRequest.executeInternal()
2)将InterceptingClientHttpRequest封装为ServiceRequestWrapper,然后使用LoadBalancerClient执行请求
这里在执行LoadBalancerClient
的execute()
方法时,首先要构造一个LoadBalancerRequest
,跟进创建方式时,发现使用lambda表达式进行构建,在调用其方法时,就会进行回调。
public interface LoadBalancerRequest<T> {
T apply(ServiceInstance instance) throws Exception;
}
构造一个LoadBalancerRequest
,并最终将HttpRequest
封装到ServiceRequestWrapper
。
调用LoadBalancerClient
的execute()
,通过负载均衡算法获取ServiceInstance
@Override
public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
String hint = getHint(serviceId);
LoadBalancerRequestAdapter<T, TimedRequestContext> lbRequest = new LoadBalancerRequestAdapter<>(request,
buildRequestContext(request, hint));
Set<LoadBalancerLifecycle> supportedLifecycleProcessors = getSupportedLifecycleProcessors(serviceId);
supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onStart(lbRequest));
// 根据服务名称以及请求获取服务实例,并使用复杂均衡策略
ServiceInstance serviceInstance = choose(serviceId, lbRequest);
if (serviceInstance == null) {
supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onComplete(
new CompletionContext<>(CompletionContext.Status.DISCARD, lbRequest, new EmptyResponse())));
throw new IllegalStateException("No instances available for " + serviceId);
}
return execute(serviceId, serviceInstance, lbRequest);
}
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;
}
}
1、在创建RestTemplate的bean时添加@LoadBalanced注解
2、LoadBalancerAutoConfiguration自动配置筛选出添加@LoadBalanced注解的RestTemplate
3、为RestTemplate设置LoadBalancerInterceptor,目的是使用LoadBalancerClient来发起请求
4、请求过程中,根据负载均衡策略,调用LoadBalancerClient.choose()方法获取最终ServiceInstance
5、根据ServiceInstance获取真实host、port,发起最后请求