SpringCloud遠(yuǎn)程服務(wù)調(diào)用三種方式及原理
一個簡單的微服務(wù)架構(gòu)圖
本文設(shè)計的 Spring Cloud 版本以及用到的 Spring Cloud 組件
- Spring Cloud Hoxton.SR5
- eureka
- feign
- ribbon

后面的內(nèi)容都將圍繞上面的圖來分析.
調(diào)用遠(yuǎn)程服務(wù)的三種方式
在 Spring Cloud 服務(wù)架構(gòu)中, 一個服務(wù)可能部署多個實例, 通常情況下, 這個時候請求一個服務(wù)接口, 是需要通過 服務(wù)名 去調(diào)用的, 比如: http://user-service/getUser.
然后在 外力 的幫助下, 通過服務(wù)名拿到多個實例的地址列表, 再借助負(fù)載均衡算法, 從地址列表中選擇一個具體的地址, 發(fā)送 HTTP 請求.
具體的做法分為如下三種:
1、基于 RestTemplate 和 @LoadBalanced 注解
RestTemplate 是 spring-web 包提供的, 用來調(diào)用 HTTP 接口的工具類, 它提供了 GET、POST 等常用的請求方法.使用方式如下:
添加到 spring 容器
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
使用前注入依賴
@Autowired private RestTemplate restTemplate;
常用 API
// 發(fā)送 GET 請求 restTemplate.getForObject(...) // 發(fā)送 POST 請求 restTemplate.postForObject(...) // 自定義 restTemplate.execute(...)
按照上面那種簡單的寫法, 我們只能調(diào)用有明確 IP 和 端口 的接口, 要想實現(xiàn)我們的需求, 至少要做兩件事情:
- 根據(jù)服務(wù)名拿到服務(wù)實例的信息
- 負(fù)載均衡算法
RestTemplate 提供了攔截器的功能 ClientHttpRequestInterceptor, 開發(fā)者可以 手動編碼 實現(xiàn)上面兩個功能. Spring Cloud 已經(jīng)幫我們實現(xiàn)了這個功能.使用方式如下:
在原有基礎(chǔ)上加上 @LoadBalanced 注解
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
調(diào)用接口時,傳入服務(wù)名稱
User user = restTemplate.getForObject("http://user-service/getUser", User.class);一個注解就幫我們完成了負(fù)載均衡.
2、基于DiscoveryClient
org.springframework.cloud.client.discovery.DiscoveryClient 可以幫我們實現(xiàn)服務(wù)發(fā)現(xiàn)的功能, 只要我們拿到服務(wù)對應(yīng)的實例信息, 后面 負(fù)載均衡 可以手動編碼實現(xiàn).
注入依賴
@Autowired private DiscoveryClient discoveryClient;
獲取注冊中心服務(wù)實例列表
List<ServiceInstance> instances = discoveryClient.getInstances("user-service");選取一個實例的地址信息, 發(fā)送請求
3、基于 Feign 的聲明式調(diào)用
在啟動類上加對應(yīng)的注解.
@EnableFeignClients
聲明接口
@FeignClient("user-service")
public interface UserFeignClient {
@GetMapping("/getUser")
User getUser();
}
原理分析
關(guān)于源碼分析部分, 本文并不會逐行分析, 只會把 關(guān)鍵方法 注釋說明(如果讀者自行 debug, 是不會迷路的.), 中間很多無聊的方法跳轉(zhuǎn)的過程都省略了.
RestTemplate 與 @LoadBalanced 注解的帶來的 “化學(xué)反應(yīng)”
先看一下大致的實現(xiàn)思路.

1、以 @LoadBalanced 為入口開啟源碼之旅
源碼注釋的大概意思是, 在 RestTemplate 上加上這個注解, 就能使用 LoadBalancerClient 接口 做一些事情, 通過查看這個接口的注釋, 它能提供的能力跟負(fù)載均衡相關(guān).
所以,到這里我們已經(jīng)清楚的了解到 @LoadBalanced 注解能為我們提供 負(fù)載均衡 的能力, 下面就需要弄清楚底層是如何實現(xiàn)負(fù)載均衡的.
Annotation to mark a RestTemplate or WebClient bean to be configured to use a LoadBalancerClient
通過查看源代碼, 我們在如下兩個地方看到了 @LoadBalanced 的使用, 通過調(diào)試發(fā)現(xiàn), 斷點根本沒有走到第二個地方.
public class LoadBalancerAutoConfiguration {
@LoadBalanced
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList();
}
public class LoadBalancerWebClientBuilderBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
if (bean instanceof WebClient.Builder) {
if (context.findAnnotationOnBean(beanName, LoadBalanced.class) == null) {
return bean;
}
((WebClient.Builder) bean).filter(exchangeFilterFunction);
}
return bean;
}
}
所以我們還是要把目光聚焦到下面的源代碼:
public class LoadBalancerAutoConfiguration {
@LoadBalanced
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList();
}
這里我通過描述這塊代碼的邏輯, 來引出一個有趣的 Spring 相關(guān)的知識點(關(guān)于這個知識點原理, 可以先直接跳到文末 Spring @Qualifier 注解的妙用):
首先, 我們應(yīng)該知道, 通過如下方式, 我們可以把 Spring 容器中的所有 RestTemplate 類型的 Bean 對象添加到下面的集合中.
@Autowired private List<RestTemplate> restTemplates = Collections.emptyList();
而我們在上面的基礎(chǔ)上再加上 @LoadBalanced 注解, 那么這個集合收集的元素就加了一層限制條件, 集合中的 Bean 不僅要是 RestTemplate 類型, 而且 Bean 在聲明時, 必須加上 @LoadBalanced 注解, 比如下面的聲明方式:
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
然后我們接著看 Spring Cloud 如何對 RestTemplate 進(jìn)行加工的
public class LoadBalancerAutoConfiguration {
@LoadBalanced
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList();
@Autowired(required = false)
private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList();
// 第一步: 遍歷 restTemplates 集合
@Bean
public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
return () -> restTemplateCustomizers.ifAvailable(customizers -> {
for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
// RestTemplateCustomizer#customize
for (RestTemplateCustomizer customizer : customizers) {
customizer.customize(restTemplate);
}
}
});
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
static class LoadBalancerInterceptorConfig {
// 第二步: 進(jìn)行自定義操作, 也就是把 LoadBalancerInterceptor 這個我們文章開頭提到的攔截器設(shè)置進(jìn)去.
@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(
final LoadBalancerInterceptor loadBalancerInterceptor) {
return restTemplate -> {
List<ClientHttpRequestInterceptor> list = new ArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
};
}
}到此為止, 程序啟動前的一些關(guān)鍵步驟已經(jīng)搞清楚了, 下面繼續(xù)分析調(diào)用流程.
2、請求調(diào)用流程
源碼入口:
User user = restTemplate.getForObject("http://user-service/getUser", User.class);順著 getForObject 進(jìn)到關(guān)鍵方法
public class RestTemplate {
// doExecute
protected <T> T doExecute(URI url, @Nullable HttpMethod method, @Nullable RequestCallback requestCallback,
@Nullable ResponseExtractor<T> responseExtractor) throws RestClientException {
// 創(chuàng)建請求對象
// 這里最終其實通過 InterceptingClientHttpRequestFactory#createRequest 方法
// 創(chuàng)建了 InterceptingClientHttpRequest
ClientHttpRequest request = createRequest(url, method);
response = request.execute();
}
}緊接著 看 InterceptingClientHttpRequest 的 execute 方法
class InterceptingClientHttpRequest extends AbstractBufferingClientHttpRequest {
// 第一步: 執(zhí)行完父類的 execute 方法后, 會來到這里.
@Override
protected final ClientHttpResponse executeInternal(HttpHeaders headers, byte[] bufferedOutput) throws IOException {
InterceptingRequestExecution requestExecution = new InterceptingRequestExecution();
return requestExecution.execute(this, bufferedOutput);
}
private class InterceptingRequestExecution implements ClientHttpRequestExecution {
private final Iterator<ClientHttpRequestInterceptor> iterator;
public InterceptingRequestExecution() {
this.iterator = interceptors.iterator();
}
// 第二步: 先 執(zhí)行前面設(shè)置的攔截器 LoadBalancerInterceptor 通過 服務(wù)名, + 負(fù)載均衡 , 拿到其中一個實例的請求地址.
// 然后根據(jù)真實的地址, 發(fā)送 http 請求.
@Override
public ClientHttpResponse execute(HttpRequest request, byte[] body) throws IOException {
// 先 執(zhí)行前面設(shè)置的攔截器 LoadBalancerInterceptor 通過 服務(wù)名, + 負(fù)載均衡 , 拿到其中一個實例的請求地址.
nextInterceptor.intercept(request, body, this);
// 然后根據(jù)真實的地址, 發(fā)送 http 請求. AbstractClientHttpRequest#execute
return delegate.execute();
}
}
}
}LoadBalancerInterceptor 的負(fù)載均衡處理
到這里, 我們就可以回答開頭提到的問題: @LoadBalanced 是如何給 RestTemplate 提供負(fù)載均衡能力的, 眾所周知 Ribbon 的能力就 負(fù)載均衡.
源碼再往后看就是 Ribbon 的領(lǐng)域了, 我們不再繼續(xù)深究. 后面可以單獨(dú)寫一篇文章對 Ribbon 的原理和源碼進(jìn)行分析.
public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {
// 看到這里, 我們應(yīng)該回想到一開始說的, @LoadBalanced 注解的相關(guān)注釋說明.
// 加上 @LoadBalanced 注解, 我們就能給 RestTemplate 賦予負(fù)載均衡的能力.
private LoadBalancerClient loadBalancer;
@Override
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
final ClientHttpRequestExecution execution) throws IOException {
// 因為我們集成了 Ribbon、 所以這里 loadBalancer 就是 RibbonLoadBalancerClient
return this.loadBalancer.execute(serviceName,
this.requestFactory.createRequest(request, body, execution));
}
}Spring @Qualifier 注解的妙用
/**
* This annotation may be used on a field or parameter as a qualifier for
* candidate beans when autowiring. It may also be used to annotate other
* custom annotations that can then in turn be used as qualifiers.
*/
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Qualifier {
String value() default "";
}不管是根據(jù)上面的注釋, 還是我們的使用經(jīng)驗來講, 我們都應(yīng)該知道 @Qualifier這個注解:它起到的是限定, “精確匹配”Bean 的作用,比如: 當(dāng)同一類型的 Bean 有多個不同實例時,可通過此注解來做 篩選或匹配。
然后再來看下這個注解的一段注釋:
It may also be used to annotate other custom annotations that can then in turn be used as qualifiers.
簡單翻一下就是: @Qualifier 可以注解其他 自定義的注解, 然后這些 自定義注解 就可以反過來為我們注入 Bean 時, 起到限定的作用(上面已經(jīng)講過它限定了什么).
于是我們再回過頭看下 @LoadBalanced 注解源碼:
@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public @interface LoadBalanced {
}從上可以看出, 這個自定義注解上是包含 @Qualifier, 所以 @LoadBalanced 注解是可以在我們注入 bean 時, 起到限定作用的.
關(guān)于 @Qualifier詳細(xì)的源碼和原理分析 可以圍繞 QualifierAnnotationAutowireCandidateResolver 這個類做檢索, 這里不再詳細(xì)闡述.
到此這篇關(guān)于SpringCloud遠(yuǎn)程服務(wù)調(diào)用三種方式及原理的文章就介紹到這了,更多相關(guān)SpringCloud服務(wù)調(diào)用內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java SpringBoot詳解集成以及配置Swagger流程
Swagger 是一個規(guī)范和完整的框架,用于生成、描述、調(diào)用和可視化 RESTful 風(fēng)格的 Web 服務(wù)??傮w目標(biāo)是使客戶端和文件系統(tǒng)作為服務(wù)器以同樣的速度來更新。文件的方法,參數(shù)和模型緊密集成到服務(wù)器端的代碼,允許API來始終保持同步2021-10-10

