注解 @EnableFeignClients 工作原理
概述
源代碼解析
注解`@EnableFeignClients`:掃描和注冊`feign`客戶端`bean`定義
`FeignClientsRegistrar` : `feign`客戶端注冊器
`#registerBeanDefinitions` -- 注冊`feign`客戶端配置和`feign`客戶端
`#registerDefaultConfiguration`-- 注冊`feign`客戶端缺省配置
`#registerClientConfiguration` -- 注冊`feign`客戶端配置
`#registerFeignClients` -- 注冊各個`feign`客戶端及其配置
`#registerFeignClient` -- 注冊一個`feign`客戶端
`FeignClientFactoryBean`生成`feign`客戶端代理對象
`FeignClientFactoryBean#getObject`生成`feign`客戶端代理對象
方法`FeignClientFactoryBean#feign` -- 創建`feign`客戶端構建器
方法`FeignClientFactoryBean#loadBalance` -- 生成具備負載均衡能力的`feign`客戶端
總結
參考文章
概述
在Spring cloud應用中,當我們要使用feign客戶端時,一般要做以下三件事情 :
使用注解@EnableFeignClients啟用feign客戶端;
示例 :
@SpringBootApplication
@EnableFeignClients
public class TestApplication {
? ? public static void main(String[] args) {
? ? ? ? SpringApplication.run(TestApplication.class, args);
? ? }
}
1
2
3
4
5
6
7
使用注解@FeignClient 定義feign客戶端 ;
示例 : 該例子定義了一個feign客戶端,將遠程服務http://test-service/test/echo映射為一個本地Java方法調用。
@FeignClient(name = "test-service", path = "/test")
public interface TestService {
? ? @RequestMapping(value = "/echo", method = RequestMethod.GET)
? ? TestModel echo(@RequestParam("parameter") String parameter);
}
1
2
3
4
5
使用注解@Autowired使用上面所定義feign的客戶端 ;
? ? @Autowired ??
? ? TestService testService;
? ? public void run()
? ? {
? ? ? ? // 這里的使用本地Java API的方式調用遠程的Restful接口
? ? ? ? TestModel dto = testService.echo("Hello,你好!");
? ? ? ? log.info("echo : {}", dto);
? ? ?}
1
2
3
4
5
6
7
8
9
上面的三個步驟,前兩個步驟可以理解為定義feign客戶端,第三步是使用所定義的feign客戶端。通過調試發現,上面第三步所注入的testService是一個代理對象,如下所示 :
testService = {$Proxy66@5502}?
?? ?"HardCodedTarget(type=TestService, name=test-service, url=http://test-service/test)"
?h = {ReflectiveFeign$FeignInvocationHandler@6924}?
? target = {Target$HardCodedTarget@6930}?
? dispatch = {LinkedHashMap@6931} ?size = 1
? ?0 = {LinkedHashMap$Entry@6948}?
? ??? ?"public abstract xxx.model.TestModel xxx.service.TestService.echo(java.lang.String)"?
1
2
3
4
5
6
7
該對象會代理客戶端完成遠程服務方法的調用,那么,該代理對象是如何生成的 ?這篇文章,我們通過源代碼分析來回答這些問題。
源代碼解析
源代碼版本 : spring-cloud-openfeign-core-2.1.0.RELEASE , Spring Cloud Greenwich.RELEASE
注解@EnableFeignClients:掃描和注冊feign客戶端bean定義
注解@EnableFeignClients告訴框架掃描所有使用注解@FeignClient定義的feign客戶端。它又通過注解@Import導入了類FeignClientsRegistrar( feign客戶端注冊器),如下所示:
@EnableFeignClients?
?? ?=> @Import(FeignClientsRegistrar.class)
1
2
那么 FeignClientsRegistrar 又是做什么的呢 ?我們繼續。
FeignClientsRegistrar : feign客戶端注冊器
FeignClientsRegistrar實現了接口 ImportBeanDefinitionRegistrar。而ImportBeanDefinitionRegistrar的設計目的,就是被某個實現類實現,配合使用@Configuration注解的使用者配置類,在配置類被處理時,用于額外注冊一部分bean定義:
對于上面的例子,使用者配置類就是 TestApplication
public interface ImportBeanDefinitionRegistrar {
? ?/**
? ? * Register bean definitions as necessary based on the given annotation metadata of
? ? * the importing @Configuration class.
? ? * 根據使用者配置類的注解元數據注冊bean定義
? ? * @param importingClassMetadata 使用者配置類的注解元數據
? ? * @param registry 當前bean定義注冊表,一般指當前Spring應用上下文對象,當前Spring容器
? ? */
? ?public void registerBeanDefinitions(
? ??? ??? ?AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
#registerBeanDefinitions – 注冊feign客戶端配置和feign客戶端
方法FeignClientsRegistrar#registerBeanDefinitions實現如下:
? ?@Override
? ?public void registerBeanDefinitions(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {
? ??? ?// 注冊缺省配置到容器 registry
? ??? ?registerDefaultConfiguration(metadata, registry);
? ??? ?// 注冊所發現的各個 feign 客戶端到到容器 registry
? ??? ?registerFeignClients(metadata, registry);
? ?}
1
2
3
4
5
6
7
#registerDefaultConfiguration– 注冊feign客戶端缺省配置
?? ?// 注冊feign客戶端的缺省配置,缺省配置信息來自注解元數據的屬性 defaultConfiguration ? ?
? ? private void registerDefaultConfiguration(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {
?? ??? ?// 獲取注解@EnableFeignClients的注解屬性 ? ??
?? ??? ?Map<String, Object> defaultAttrs = metadata
?? ??? ??? ??? ?.getAnnotationAttributes(EnableFeignClients.class.getName(), true);
?? ??? ?if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
?? ??? ??? ?String name;
?? ??? ??? ?// 下面是對所注冊的缺省配置的的命名,格式如下 :
?? ??? ??? ?// default.xxx.TestApplication
?? ??? ??? ?if (metadata.hasEnclosingClass()) {
?? ??? ??? ??? ?// ?針對注解元數據metadata對應一個內部類或者方法返回的方法本地類的情形
?? ??? ??? ??? ?name = "default." + metadata.getEnclosingClassName();
?? ??? ??? ?}
?? ??? ??? ?else { ? ? ? ?
?? ??? ??? ??? ?// name 舉例 : default.xxx.TestApplication
?? ??? ??? ??? ?// 這里 xxx.TestApplication 是注解@EnableFeignClients所在配置類的長名稱?? ??? ??? ?
?? ??? ??? ??? ?name = "default." + metadata.getClassName();
?? ??? ??? ?}
?? ??? ??? ?// 各種信息準備就緒,現在執行注冊
?? ??? ??? ?registerClientConfiguration(registry, name,
?? ??? ??? ??? ??? ?defaultAttrs.get("defaultConfiguration"));
?? ??? ?}
?? ?}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#registerDefaultConfiguration方法最終注冊客戶端缺省配置的動作交給方法#registerClientConfiguration執行。
#registerClientConfiguration – 注冊feign客戶端配置
? ? // 將指定feign客戶端配置configuration作為一個bean定義注冊到容器:
? ? // bean 定義對象類型 : GenericBeanDefinition
? ? // bean class : FeignClientSpecification ? ?
? ? // bean name : default.xxx.TestApplication.FeignClientSpecification (缺省配置)
? ? // bean name : test-service.FeignClientSpecification (針對某個feign client 的配置)
?? ?private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,
?? ??? ??? ?Object configuration) {
?? ??? ?BeanDefinitionBuilder builder = BeanDefinitionBuilder
?? ??? ??? ??? ?.genericBeanDefinition(FeignClientSpecification.class);
?? ??? ?// 設置構造函數參數?? ??? ?
?? ??? ?builder.addConstructorArgValue(name);
?? ??? ?builder.addConstructorArgValue(configuration);
?? ??? ?// 從bean定義構建器構造bean定義并注冊到容器
?? ??? ?registry.registerBeanDefinition(
?? ??? ??? ??? ?name + "." + FeignClientSpecification.class.getSimpleName(),
?? ??? ??? ??? ?builder.getBeanDefinition());
?? ?}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#registerClientConfiguration方法用于注冊一個feign客戶端配置bean,可以用于注冊針對所有feign客戶端的缺省配置的注冊,也可以用于針對每個feign客戶端的專有配置的注冊。
針對所有feign客戶端的缺省配置的bean名稱類似于 : default.xxx.TestApplication.FeignClientSpecification,
針對某個名稱為test-service的feign客戶端的配置的bean名稱類似于:test-service.FeignClientSpecification。
#registerFeignClients – 注冊各個feign客戶端及其配置
?? ?// 參數 metadata : 注解@EnableFeignClients所在配置類的注解元數據
?? ?public void registerFeignClients(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {
?? ??? ?// 定義一個基于classpath的組件掃描器,它會根據指定的掃描位置和@EnableFeignClients注解屬性 ??
?? ??? ?// 找出開發人員定義的所有feign客戶端,也就是那些使用了注解@FeignClient的所有接口定義
?? ??? ?ClassPathScanningCandidateComponentProvider scanner = getScanner();
?? ??? ?scanner.setResourceLoader(this.resourceLoader);
?? ??? ?Set<String> basePackages;
?? ??? ?// attrs 用于表示注解@EnableFeignClients所在配置類的注解元數據中注解@EnableFeignClients
?? ??? ?// 的部分
?? ??? ?Map<String, Object> attrs = metadata
?? ??? ??? ??? ?.getAnnotationAttributes(EnableFeignClients.class.getName());
?? ??? ?AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
?? ??? ??? ??? ?FeignClient.class);
?? ??? ?final Class<?>[] clients = attrs == null ? null
?? ??? ??? ??? ?: (Class<?>[]) attrs.get("clients");
?? ??? ?if (clients == null || clients.length == 0) {
? ? ? ? ? ?// @EnableFeignClients 中沒有指定 clients 屬性的情況
?? ??? ??? ?scanner.addIncludeFilter(annotationTypeFilter);
?? ??? ??? ?basePackages = getBasePackages(metadata);
?? ??? ?}
?? ??? ?else {
? ? ? ? ? ?// @EnableFeignClients 中指定了 clients 屬性的情況
?? ??? ??? ?final Set<String> clientClasses = new HashSet<>();
?? ??? ??? ?basePackages = new HashSet<>();
?? ??? ??? ?for (Class<?> clazz : clients) {
?? ??? ??? ??? ?basePackages.add(ClassUtils.getPackageName(clazz));
?? ??? ??? ??? ?clientClasses.add(clazz.getCanonicalName());
?? ??? ??? ?}
?? ??? ??? ?AbstractClassTestingTypeFilter filter = new AbstractClassTestingTypeFilter() {
?? ??? ??? ??? ?@Override
?? ??? ??? ??? ?protected boolean match(ClassMetadata metadata) {
?? ??? ??? ??? ??? ?String cleaned = metadata.getClassName().replaceAll("\\$", ".");
?? ??? ??? ??? ??? ?return clientClasses.contains(cleaned);
?? ??? ??? ??? ?}
?? ??? ??? ?};
?? ??? ??? ?scanner.addIncludeFilter(
?? ??? ??? ??? ??? ?new AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));
?? ??? ?}
?? ??? ?// 使用 scanner 掃描每一個 basePackage, 獲取其中的 feign 客戶端定義,?
?? ??? ?// 也就是 @FeignClient 定義的那些接口
?? ??? ?for (String basePackage : basePackages) {
?? ??? ??? ?Set<BeanDefinition> candidateComponents = scanner
?? ??? ??? ??? ??? ?.findCandidateComponents(basePackage);
?? ??? ??? ?for (BeanDefinition candidateComponent : candidateComponents) {
?? ??? ??? ??? ?if (candidateComponent instanceof AnnotatedBeanDefinition) {
?? ??? ??? ??? ??? ?// verify annotated class is an interface
?? ??? ??? ??? ??? ?AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
?? ??? ??? ??? ??? ?AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
?? ??? ??? ??? ??? ?Assert.isTrue(annotationMetadata.isInterface(),
?? ??? ??? ??? ??? ??? ??? ?"@FeignClient can only be specified on an interface");
?? ??? ??? ??? ??? ?// 獲取所定義的feign客戶端接口上的注解@FeignClient屬性
?? ??? ??? ??? ??? ?Map<String, Object> attributes = annotationMetadata
?? ??? ??? ??? ??? ??? ??? ?.getAnnotationAttributes(
?? ??? ??? ??? ??? ??? ??? ??? ??? ?FeignClient.class.getCanonicalName());
?? ??? ??? ??? ??? ?String name = getClientName(attributes);
?? ??? ??? ??? ??? ?// 將所定義的feign客戶端上的配置屬性作為一個bean注冊到容器 ??
?? ??? ??? ??? ??? ?// 此方法的邏輯我們上面已經分析過
?? ??? ??? ??? ??? ?registerClientConfiguration(registry, name,
?? ??? ??? ??? ??? ??? ??? ?attributes.get("configuration"));
?? ??? ??? ??? ??? ?// 將所定義的feign客戶端作為一個bean注冊到容器:
?? ??? ??? ??? ??? ?// bean 定義類型 : GenericBeanDefinition
?? ??? ??? ??? ??? ?// ?bean class : FeignClientFactoryBean
?? ??? ??? ??? ??? ?// ?autowire 模式 : 根據類型綁定
?? ??? ??? ??? ??? ?// @FeignClient注解中的url,path,fallback等屬性會設置為bean定義的屬性
?? ??? ??? ??? ??? ?registerFeignClient(registry, annotationMetadata, attributes);
?? ??? ??? ??? ?}
?? ??? ??? ?}
?? ??? ?}
?? ?} ??
?? ?// 輔助工具類,從@EnableFeignClients注解屬性中獲取basePackages屬性:
?? ?// 參考以下@EnableFeignClients注解屬性 :
?? ?// 1. value
?? ?// 2. basePackages
?? ?// 3. basePackageClasses
?? ?// 4. 配置類所在的包
?? ?// 參數 importingClassMetadata : 使用注解@EnableFeignClients的配置類的元數據
?? ?protected Set<String> getBasePackages(AnnotationMetadata importingClassMetadata) {
?? ??? ?// 注解@EnableFeignClients的屬性
?? ??? ?Map<String, Object> attributes = importingClassMetadata
?? ??? ??? ??? ?.getAnnotationAttributes(EnableFeignClients.class.getCanonicalName());
?? ??? ?Set<String> basePackages = new HashSet<>();
?? ??? ?for (String pkg : (String[]) attributes.get("value")) {
?? ??? ??? ?if (StringUtils.hasText(pkg)) {
?? ??? ??? ??? ?basePackages.add(pkg);
?? ??? ??? ?}
?? ??? ?}
?? ??? ?for (String pkg : (String[]) attributes.get("basePackages")) {
?? ??? ??? ?if (StringUtils.hasText(pkg)) {
?? ??? ??? ??? ?basePackages.add(pkg);
?? ??? ??? ?}
?? ??? ?}
?? ??? ?for (Class<?> clazz : (Class[]) attributes.get("basePackageClasses")) {
?? ??? ??? ?basePackages.add(ClassUtils.getPackageName(clazz));
?? ??? ?}
?? ??? ?if (basePackages.isEmpty()) {
?? ??? ??? ?basePackages.add(
?? ??? ??? ??? ??? ?ClassUtils.getPackageName(importingClassMetadata.getClassName()));
?? ??? ?}
?? ??? ?return basePackages;
?? ?}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
#registerFeignClients 最終注冊feign客戶端配置的動作交給#registerClientConfiguration完成,而注冊feign客戶端的動作交給#registerFeignClient方法完成。
#registerFeignClient – 注冊一個feign客戶端
?? ?// 將所定義的feign客戶端作為一個bean注冊到容器:
?? ?// bean 定義類型 : GenericBeanDefinition
?? ?// ?bean class : FeignClientFactoryBean -- 這是一個工廠bean,而不是最終bean實例的class
?? ?// ?autowire 模式 : 根據類型綁定
?? ?// @FeignClient注解中的url,path,fallback等屬性會設置為bean定義的屬性
?? ?// 參數 registry : Spring 容器
?? ?// 參數 annotationMetadata : @FeignClient所注解的接口上的注解元數據
?? ?// 參數 attributes : @FeignClient 注解屬性信息
?? ?private void registerFeignClient(BeanDefinitionRegistry registry,
? ??? ??? ?AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
? ??? ?String className = annotationMetadata.getClassName();
? ??? ?BeanDefinitionBuilder definition = BeanDefinitionBuilder
? ??? ??? ??? ?.genericBeanDefinition(FeignClientFactoryBean.class);
? ??? ?validate(attributes);
? ??? ?definition.addPropertyValue("url", getUrl(attributes));
? ??? ?definition.addPropertyValue("path", getPath(attributes));
? ??? ?String name = getName(attributes);
? ??? ?definition.addPropertyValue("name", name);
? ??? ?definition.addPropertyValue("type", className);
? ??? ?definition.addPropertyValue("decode404", attributes.get("decode404"));
? ??? ?definition.addPropertyValue("fallback", attributes.get("fallback"));
? ??? ?definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
? ??? ?definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
? ??? ?String alias = name + "FeignClient";
? ??? ?AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
? ??? ?boolean primary = (Boolean)attributes.get("primary"); // has a default, won't be null
? ??? ?beanDefinition.setPrimary(primary);
? ??? ?String qualifier = getQualifier(attributes);
? ??? ?if (StringUtils.hasText(qualifier)) {
? ??? ??? ?alias = qualifier;
? ??? ?}
? ??? ?BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
? ??? ??? ??? ?new String[] { alias });
? ??? ?BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
? ?}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
從上面的代碼分析可知,FeignClientsRegistrar的主要作用如下 :
注冊缺省feign客戶端配置bean定義;
對于每個@FeignClient注解的feign客戶端定義 :
注冊一個針對該feign客戶端的配置bean定義;
注冊該feign客戶端bean定義,指定生成bean實例采用工廠類FeignClientFactoryBean;
而且,上述功能實現在類方法FeignClientsRegistrar#registerBeanDefinitions中,這是接口ImportBeanDefinitionRegistrar所定義的方法。該方法會在@EnableFeignClients注解被處理時執行。具體的執行時調用棧如下所示:
? ? AbstractApplicationContext#invokeBeanFactoryPostProcessors
? ? => PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors
? ? => foreach BeanDefinitionRegistryPostProcessor : #postProcessBeanDefinitionRegistry
? ? => ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry
? ? => #processConfigBeanDefinitions
? ? => ConfigurationClassBeanDefinitionReader#loadBeanDefinitions
? ? => foreach ConfigurationClass : #loadBeanDefinitionsForConfigurationClass
? ? => #loadBeanDefinitionsFromRegistrars
? ? => foreach ImportBeanDefinitionRegistrar : #registerBeanDefinitions
? ? => FeignClientsRegistrar#registerBeanDefinitions
1
2
3
4
5
6
7
8
9
10
FeignClientFactoryBean生成feign客戶端代理對象
基于上面的分析,我們可以得知,開發人員所定義的feign客戶端和相關配置會以bean定義的形式注冊到bean容器中,這樣當使用@Autowired注入一個feign客戶端時,容器會使用工廠類FeignClientFactoryBean為其生成一個實例。下面我們來看其具體工作過程。
FeignClientFactoryBean#getObject生成feign客戶端代理對象
? ? // 該方法由接口FactoryBean約定
? ??? ?@Override
?? ?public Object getObject() throws Exception {
?? ??? ?return getTarget();
?? ?}
?? ?
?? ?
?? ?<T> T getTarget() {
? ? ? ? // ?從應用上下文中獲取創建 feign 客戶端的上下文對象 FeignContext
? ? ? ? // FeignContext 針對每個feign客戶端定義會生成一個不同的 AnnotationConfigApplicationContext,
? ? ? ? // 這些應用上下文的parent都設置為當前應用的主應用上下文
? ? ? ? // 參考 : FeignAutoConfiguration
?? ??? ?FeignContext context = applicationContext.getBean(FeignContext.class);
? ? ? ? // 為目標feign客戶端對象構建一個 builder,該builder最終生成的目標feign客戶端是一個
? ? ? ? // 動態代理,使用 InvocationHandler : ReflectiveFeign$FeignInvocationHandler
?? ??? ?Feign.Builder builder = feign(context);
?? ??? ?if (!StringUtils.hasText(this.url)) {
? ? ? ? ? ?// @FeignClient 屬性 url 屬性沒有指定的情況 ? ? ? ??
? ? ? ? ? ?// 根據屬性 name , path 拼裝一個 url,
? ? ? ? ? ?// 這種通常是需要在多個服務節點之間進行負載均衡的情況
?? ??? ??? ?if (!this.name.startsWith("http")) {
?? ??? ??? ??? ?url = "http://" + this.name;
?? ??? ??? ?}
?? ??? ??? ?else {
?? ??? ??? ??? ?url = this.name;
?? ??? ??? ?}
? ? ? ? ? ?// 方法cleanPath()加工屬性path,使其以/開頭,不以/結尾
?? ??? ??? ?url += cleanPath();
? ? ? ? ? ?// 這里形成的url格式類似 : ?http://test-service/test
? ? ? ? ? ?// 其中 test-service 是服務名,不是服務所在節點的IP,主機名或者域名
? ? ? ? ? ?
? ? ? ? ? ?// 函數 loadBalance 做如下動作 :
? ? ? ? ? ?// 1. 將builder和一個LoadBalancerFeignClient bean實例關聯起來
? ? ? ? ? ?// 2. 使用一個HystrixTargeter將builder和一個 HardCodedTarget bean實例關聯起來
? ? ? ? ? ?// 這里 HardCodedTarget 表示對應 url 為 http://test-service/test 的遠程服務(可能
? ? ? ? ? ?// 包含多個服務方法)
? ? ? ? ? ?// 3. 生成最終的feign client 實例 : ReflectiveFeign$FeignInvocationHandler 的動態代理對象,
? ? ? ? ? ?// 使用 InvocationHandler : ReflectiveFeign$FeignInvocationHandler。
? ? ? ? ? ?// 每個遠程服務方法會對應到一個@FeignClient注解的接口方法上(依據方法上的注解進行匹配)
?? ??? ??? ?return (T) loadBalance(builder, context, new HardCodedTarget<>(this.type,
?? ??? ??? ??? ??? ?this.name, url));
?? ??? ?}
? ? ? ??
? ? ? ? // @FeignClient 屬性 url 屬性被指定的情況?
? ? ? ? // 這種通常是明確指出了服務節點的url的情況,實際上不需要負載均衡
?? ??? ?if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
?? ??? ??? ?this.url = "http://" + this.url;
?? ??? ?}
?? ??? ?String url = this.url + cleanPath();
? ? ? ? // 將builder和一個LoadBalancerFeignClient bean實例關聯起來
?? ??? ?Client client = getOptional(context, Client.class);
?? ??? ?if (client != null) {
?? ??? ??? ?if (client instanceof LoadBalancerFeignClient) {
?? ??? ??? ??? ?// not load balancing because we have a url,
?? ??? ??? ??? ?// but ribbon is on the classpath, so unwrap
?? ??? ??? ??? ?// 因為指定了明確的服務節點url,所以這里不需要負載均衡,
?? ??? ??? ??? ?// 所以這里盡管client是LoadBalancerFeignClient,所以
?? ??? ??? ??? ?// 實際上可以獲取其所代理的對象作為最終的client,
?? ??? ??? ??? ?// 相當于去掉了LoadBalancerFeignClient這層的代理功能
?? ??? ??? ??? ?client = ((LoadBalancerFeignClient)client).getDelegate();
?? ??? ??? ?}
?? ??? ??? ?builder.client(client);
?? ??? ?}
? ? ? ? // 使用一個HystrixTargeter將builder和一個 HardCodedTarget bean實例關聯起來
?? ??? ?Targeter targeter = get(context, Targeter.class);
? ? ? ? // 生成最終的feign client 實例 : ReflectiveFeign$FeignInvocationHandler 的動態代理對象,
? ? ? ? // 使用 InvocationHandler : ReflectiveFeign$FeignInvocationHandler。
? ? ? ? // 每個遠程服務方法會對應到 一個@FeignClient注解的接口方法上(依據方法上的注解進行匹配) ? ? ? ?
?? ??? ?return (T) targeter.target(this, builder, context, new HardCodedTarget<>(
?? ??? ??? ??? ?this.type, this.name, url));
?? ?}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
方法FeignClientFactoryBean#feign – 創建feign客戶端構建器
protected Feign.Builder feign(FeignContext context) {
?? ??? ?FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
?? ??? ?Logger logger = loggerFactory.create(this.type);
?? ??? ?// 從上下文獲取一個 Feign.Builder 上,
?? ??? ?// 并從上下文獲得 Encoder, Decoder, Contract 設置到該 builder 上
?? ??? ?Feign.Builder builder = get(context, Feign.Builder.class)
?? ??? ??? ??? ?// required values
?? ??? ??? ??? ?.logger(logger)
?? ??? ??? ??? ?.encoder(get(context, Encoder.class))
?? ??? ??? ??? ?.decoder(get(context, Decoder.class))
?? ??? ??? ??? ?.contract(get(context, Contract.class));
?? ??? ?
?? ??? ?// 對 builder 進行其他屬性設置
?? ??? ?configureFeign(context, builder);
?? ??? ?return builder;
?? ?}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
方法FeignClientFactoryBean#loadBalance – 生成具備負載均衡能力的feign客戶端
為feign客戶端構建器綁定負載均衡客戶端,綁定目標服務端點,并生成最終的feign客戶端實例。
// 對builder設置負載均衡客戶端,綁定到目標服務端點,構建最終的feign客戶端對象
protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
?? ??? ??? ?HardCodedTarget<T> target) {
?? ??? ?// 從上下文context獲取一個Client,缺省是 LoadBalancerFeignClient ?? ?
?? ??? ?Client client = getOptional(context, Client.class);
?? ??? ?if (client != null) {
?? ??? ??? ?// 將client設置到builder上
?? ??? ??? ?builder.client(client);
?? ??? ??? ?// 從上下文中獲取一個 targeter,缺省是一個 HystrixTargeter
?? ??? ??? ?Targeter targeter = get(context, Targeter.class);
?? ??? ??? ?// 上面獲取得到的 targeter 會根據 builder 的類型決定如何將 target
?? ??? ??? ?// 綁定到 builder 并設置有關的其他屬性和功能,然后生成最終的feign客戶端對象
?? ??? ??? ?return targeter.target(this, builder, context, target);
?? ??? ?}
?? ??? ?throw new IllegalStateException(
?? ??? ??? ?"No Feign Client for loadBalancing defined. Did you forget to include " +
?? ??? ??? ?"spring-cloud-starter-netflix-ribbon?");
?? ?}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
從上面分析可以看出,缺省情況下,所使用的feign客戶端構建器類為Feign.Builder,并且Targeter是一個HystrixTargeter。HystrixTargeter#target方法的參數builder為Feign.Builder時,會直接調用該builder的target方法,如下所示 :
class HystrixTargeter implements Targeter {
@Override
?? ?public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
?? ??? ??? ??? ??? ??? ?Target.HardCodedTarget<T> target) {
?? ??? ?if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) {
?? ??? ??? ?return feign.target(target);
?? ??? ?}
? ? ? ?// ... 省略其他代碼?? ??? ??? ?
?? ?}
}
1
2
3
4
5
6
7
8
9
10
接下來再來看Feign.Builder#target是如何工作的:
? // 執行構建并且創建相應的feign客戶端實例?? ?
? public <T> T target(Target<T> target) {
? ? ? return build().newInstance(target);
? ? }
?
? // 構建過程,最終根據各種配置生成一個 ReflectiveFeign 對象?? ? ??
? public Feign build() {
? ? ? SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
? ? ? ? ? new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
? ? ? ? ? ? ? logLevel, decode404, closeAfterDecode, propagationPolicy);
? ? ? ParseHandlersByName handlersByName =
? ? ? ? ? new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,
? ? ? ? ? ? ? errorDecoder, synchronousMethodHandlerFactory);
? ? ? return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
? ? }
? } ? ?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
然后再看ReflectiveFeign#newInstance方法:
? // 創建最終的feign客戶端實例 : 一個 ReflectiveFeign$FeignInvocationHandler 的動態代理對象
? @Override
? public <T> T newInstance(Target<T> target) {
? ? Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
? ? Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
? ? List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();
? ? for (Method method : target.type().getMethods()) {
? ? ? if (method.getDeclaringClass() == Object.class) {
? ? ? ? continue;
? ? ? } else if (Util.isDefault(method)) {
? ? ? ?? ?// 對于每個缺省方法,使用 DefaultMethodHandler?
? ? ? ? DefaultMethodHandler handler = new DefaultMethodHandler(method);
? ? ? ? defaultMethodHandlers.add(handler);
? ? ? ? methodToHandler.put(method, handler);
? ? ? } else {
? ? ? ?? ?// 對于每個對應服務功能端點的方法,缺省使用nameToHandler獲取的MethodHandler,缺省是
? ? ? ?? ?// SynchronousMethodHandler
? ? ? ? methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
? ? ? }
? ? }
? ? // 創建feign客戶端實例 ReflectiveFeign$FeignInvocationHandler,
? ? // 該對象包含了上面所創建的methodToHandler,用于對應各個開發者定義的@FeignClient接口方法
? ? InvocationHandler handler = factory.create(target, methodToHandler);
? ? // 創建feign客戶端實例的動態代理對象
? ? T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
? ? ? ? new Class<?>[] {target.type()}, handler);
?? ?// 將缺省方法處理器綁定到feign客戶端實例的動態代理對象上
? ? for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
? ? ? defaultMethodHandler.bindTo(proxy);
? ? }
? ? return proxy;
? }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
從上面的分析我們不難看出,為什么最終注入的testService最終是一個ReflectiveFeign$FeignInvocationHandler動態代理實例了。
總結
從上面的分析可以看出,當我們使用注解@EnableFeignClients 時,相當于啟用了feign客戶端定義的掃描和注冊機制,從而可以發現開發人員通過注解@FeignClient定義的feign客戶端,并最終作為bean定義注冊到容器中。而通過@Autowired自動裝配注解,這些feign客戶端會以ReflectiveFeign$FeignInvocationHandler動態代理的形式被注入到使用方。該feign客戶端包含了對每個接口方法的處理器MethodHandler,接口缺省方法對應DefaultMethodHandler,服務功能端點方法對應SynchronousMethodHandler。
參考文章
Spring Cloud feign客戶端執行流程概述
————————————————
原文鏈接:https://blog.csdn.net/andy_zhang2007/article/details/86680622
總結
以上是生活随笔為你收集整理的注解 @EnableFeignClients 工作原理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 使用IntelliJ IDEA搭建多ma
- 下一篇: Redis Cluster 集群模式原理