2021-01-08

博客园Logo
首页
新闻
博问
专区
闪存
班级
代码改变世界
搜索
注册
登录
bojiangzhou

英雄修身齐家治国平天下

随笔 - 53 文章 - 12 评论 - 441 0
博客园 首页 新随笔 联系 管理
SpringCloud 源码系列(6)—— 声明式服务调用 Feign
目录

一、Feign 基础入门
1、Feign 概述
2、DEMO示例
3、FeignClient 注解
4、FeignClient 核心组件
5、Feign 属性文件配置
6、FeignClient 开启日志
二、扫描 @FeignClient 注解接口
1、FeignClient 动态注册组件 FeignClientsRegistrar
2、扫描 @FeignClient 注解接口
3、@FeignClient 接口构造 BeanDefinition 并注册
4、一张图总结 @FeignClient 接口扫描流程
三、构建 @FeignClient 接口动态代理
1、构造 FeignClient 的动态代理组件 FeignClientFactoryBean
2、Feign 动态代理构造器 Feign.Builder
3、Feign 网络调用组件 Client
4、动态代理目标器 Targeter
5、Feign.Builder 创建动态代理
6、一张图总结 FeignClient 生成动态代理的流程
四、FeignClient 结合Ribbon进行负载均衡请求
1、FeignClient 动态代理请求
2、执行请求 executeAndDecode
3、LoadBalancerFeignClient 负载均衡
4、一张图总结 Feign 负载均衡请求

SpringCloud 源码系列(1)—— 注册中心 Eureka(上)

SpringCloud 源码系列(2)—— 注册中心 Eureka(中)

SpringCloud 源码系列(3)—— 注册中心 Eureka(下)

SpringCloud 源码系列(4)—— 负载均衡 Ribbon(上)

SpringCloud 源码系列(5)—— 负载均衡 Ribbon(下)

SpringCloud 源码系列(6)—— 声明式服务调用 Feign

回到顶部
一、Feign 基础入门
1、Feign 概述
在使用 Spring Cloud 开发微服务应用时,各个服务提供者都是以HTTP接口的形式对外提供服务,因此在服务消费者调用服务提供者时,底层通过 HTTP Client 的方式访问。我们可以使用JDK原生的 URLConnection、Apache的HTTP Client、OkHttp、Spring 的 RestTemplate 去实现服务间的调用。但是最方便、最优雅的方式是通过 Spring Cloud OpenFeign 进行服务间的调用。

Feign 是一个声明式的 Web Service 客户端,它的目的就是让Web Service调用更加简单。Spring Cloud 对 Feign 进行了增强,使 Feign 支持 Spring MVC 的注解,并整合了 Ribbon、Hystrix 等。Feign还提供了HTTP请求的模板,通过编写简单的接口和注解,就可以定义好HTTP请求的参数、格式、地址等信息。Feign 会完全代理HTTP的请求,在使用过程中我们只需要依赖注入Bean,然后调用对应的方法传递参数即可。Feign 的首要目标是将 Java HTTP 客户端的书写过程变得简单。

Feign 的一些主要特性如下:

可插拔的注解支持,包括Feign注解和JAX-RS注解。
支持可插拔的HTTP编码器和解码器。
支持 Hystrix 和它的Fallback。支持Ribbon的负载均衡。
支持HTTP请求和响应的压缩。
GitHub地址:

OpenFeign 地址:https://github.com/OpenFeign/feign
SpringCloud OpenFeign 地址:https://github.com/spring-cloud/spring-cloud-openfeign
2、DEMO示例
还是使用前面研究 Eureka 和 Ribbon 时的 demo-producer、demo-consumer 服务来做测试。

① 首先,需要引入 openfeign 的依赖

1
2 org.springframework.cloud
3 spring-cloud-starter-openfeign
4
spring-cloud-starter-openfeign 会帮我们引入如下依赖,包含了 OpenFeign 的核心组件。

② 在 demo-consumer 服务中,增加一个 Feign 客户端接口,来调用 demo-producer 的接口。

复制代码
1 @FeignClient(value = “demo-producer”)
2 public interface ProducerFeignClient {
3
4 @GetMapping("/v1/user/{id}")
5 ResponseEntity getUserById(@PathVariable Long id, @RequestParam(required = false) String name);
6
7 @PostMapping("/v1/user")
8 ResponseEntity createUser(@RequestBody User user);
9
10 }
复制代码
③ 在启动类加上 @EnableFeignClients 注解。

1 @EnableFeignClients
2 @SpringBootApplication
3 public class ConsumerApplication {
4 //…
5 }
④ 在接口中注入 ProducerFeignClient 就可以使用 Feign 客户端接口来调用远程服务了。

复制代码
1 @RestController
2 public class FeignController {
3 private final Logger logger = LoggerFactory.getLogger(getClass());
4
5 @Autowired
6 private ProducerFeignClient producerFeignClient;
7
8 @GetMapping("/v1/user/query")
9 public ResponseEntity queryUser() {
10 ResponseEntity result = producerFeignClient.getUserById(1L, “tom”);
11 User user = result.getBody();
12 logger.info(“query user: {}”, user);
13 return ResponseEntity.ok(user);
14 }
15
16 @GetMapping("/v1/user/create")
17 public ResponseEntity createUser() {
18 ResponseEntity result = producerFeignClient.createUser(new User(10L, “Jerry”, 20));
19 User user = result.getBody();
20 logger.info(“create user: {}”, user);
21 return ResponseEntity.ok(user);
22 }
23 }
复制代码
⑤ 在 demo-producer 服务增加 UserController 接口供消费者调用

复制代码
1 @RestController
2 public class UserController {
3 private final Logger logger = LoggerFactory.getLogger(getClass());
4
5 @PostMapping("/v1/user/{id}")
6 public ResponseEntity queryUser(@PathVariable Long id, @RequestParam String name) {
7 logger.info(“query params: id :{}, name:{}”, id, name);
8 return ResponseEntity.ok(new User(id, name, 10));
9 }
10
11 @PostMapping("/v1/user/{id}")
12 public ResponseEntity createUser(@RequestBody User user) {
13 logger.info(“create params: {}”, user);
14 return ResponseEntity.ok(user);
15 }
16 }
复制代码
⑥ 测试

先把把注册中心启起来,然后 demo-producer 启两个实例,再启动 demo-consumer,调用 demo-consumer 的接口测试,会发现,ProducerFeignClient 的调用会轮询到 demo-consumer 的两个实例上。

通过简单的测试可以发现,Feign 使得 Java HTTP 客户端的书写过程变得非常简单,就像开发接口一样。另外,Feign底层一定整合了 Ribbon,@FeignClient 指定了服务名称,请求最终一定是通过 Ribbon 的 ILoadBalancer 组件进行负载均衡的。

3、FeignClient 注解
通过前面的DEMO可以发现,使用 Feign 最核心的应该就是 @EnableFeignClients 和 @FeignClient 这两个注解,@FeignClient 加在客户端接口类上,@EnableFeignClients 加在启动类上,就是用来扫描加了 @FeignClient 接口的类。我们研究源码就从这两个入口开始。

要知道接口是不能直接注入和调用的,那么一定是 @EnableFeignClients 扫描到 @FeignClient 注解的接口后,基于这个接口生成了动态代理对象,并注入到 Spring IOC 容器中,才可以被注入使用。最终呢,一定会通过 Ribbon 负载均衡获取一个 Server,然后重构 URI,再发起最终的HTTP调用。

① @EnableFeignClients 注解

首先看 @EnableFeignClients 的类注释,注释就已经说明了,这个注解就是用来扫描 @FeignClient 注解的接口的,那么核心的逻辑应该就是在 @Import 导入的类 FeignClientsRegistrar 中的。

EnableFeignClients 的主要属性有如下:

value、basePackages: 配置扫描 @FeignClient 的包路径
clients:直接指定扫描的 @FeignClient 接口
defaultConfiguration:配置 Feign 客户端全局默认配置类,从注释可以得知,默认的全局配置类是 FeignClientsConfiguration
复制代码
1 package org.springframework.cloud.openfeign;
2
3 /**
4 * Scans for interfaces that declare they are feign clients (via
5 * {@link org.springframework.cloud.openfeign.FeignClient} @FeignClient).
6 * Configures component scanning directives for use with
7 * {@link org.springframework.context.annotation.Configuration}
8 * @Configuration classes.
9 /
10 @Retention(RetentionPolicy.RUNTIME)
11 @Target(ElementType.TYPE)
12 @Documented
13 @Import(FeignClientsRegistrar.class)
14 public @interface EnableFeignClients {
15
16 // 指定扫描 @FeignClient 包所在目录
17 String[] value() default {};
18
19 // 指定扫描 @FeignClient 包所在目录
20 String[] basePackages() default {};
21
22 // 指定标记接口来扫描包
23 Class<?>[] basePackageClasses() default {};
24
25 // Feign 客户端全局默认配置类
26 /
*
27 * A custom @Configuration for all feign clients. Can contain override
28 * @Bean definition for the pieces that make up the client, for instance
29 * {@link feign.codec.Decoder}, {@link feign.codec.Encoder}, {@link feign.Contract}.
30 *
31 * @see FeignClientsConfiguration for the defaults
32 * @return list of default configurations
33 */
34 Class<?>[] defaultConfiguration() default {};
35
36 // 直接指定 @FeignClient 注解的类,这时就会禁用类路径扫描
37 Class<?>[] clients() default {};
38 }
复制代码
② @FeignClient 注解

首先看 FeignClient 的类注释,注释说明 @FeignClient 注解就是声明一个 REST 客户端接口,而且会创建一个可以注入的组件,应该就是动态代理的bean。而且如果Ribbon可用,然后就可以用Ribbon做负载均衡,这个负载均衡可以用 @RibbonClient 定制配置类,名称一样就行。

FeignClient 注解被 @Target(ElementType.TYPE) 修饰,表示 FeignClient 注解的作用目标在接口上。@Retention(RetentionPolicy.RUNTIME) 注解表明该注解会在 Class 字节码文件中存在,在运行时可以通过反射获取到。

@FeignClient 注解用于创建声明式 API 接口,该接口是 RESTful 风格的。Feign 被设计成插拔式的,可以注入其他组件和 Feign 一起使用。最典型的是如果 Ribbon 可用,Feign 会和Ribbon 相结合进行负载均衡。

FeignClient 主要有如下属性:

name:指定 FeignClient 的名称,如果项目使用了 Ribbon,name 属性会作为微服务的名称,用于服务发现。
url:url 一般用于调试,可以手动指定 @FeignClient 调用的地址。
decode404:当发生404错误时,如果该字段为true,会调用 decoder 进行解码,否则抛出 FeignException。
configuration:FeignClient 配置类,可以自定义Feign的Encoder、Decoder、LogLevel、Contracto
fallback:定义容错的处理类,当调用远程接口失败或超时时,会调用对应接口的容错逻辑,fallback 指定的类必须实现 @FeignClient 标记的接口。
fallbackFactory:工厂类,用于生成 fallback 类实例,通过这个属性我们可以实现每个接口通用的容错逻辑,减少重复的代码。
path:定义当前 FeignClient 的统一前缀。
复制代码
1 package org.springframework.cloud.openfeign;
2
3 /**
4 * Annotation for interfaces declaring that a REST client with that interface should be
5 * created (e.g. for autowiring into another component). If ribbon is available it will be
6 * used to load balance the backend requests, and the load balancer can be configured
7 * using a @RibbonClient with the same name (i.e. value) as the feign client.
8 /
9 @Target(ElementType.TYPE)
10 @Retention(RetentionPolicy.RUNTIME)
11 @Documented
12 @Inherited
13 public @interface FeignClient {
14
15 // 指定服务名称
16 @AliasFor(“name”)
17 String value() default “”;
18
19 // 指定服务名称,已过期
20 @Deprecated
21 String serviceId() default “”;
22
23 // FeignClient 接口生成的动态代理的bean名称
24 String contextId() default “”;
25
26 // 指定服务名称
27 @AliasFor(“value”)
28 String name() default “”;
29
30 // @Qualifier 标记
31 String qualifier() default “”;
32
33 // 如果不使用Ribbon负载均衡,就需要使用url返回一个绝对地址
34 String url() default “”;
35
36 // 404 默认抛出 FeignExceptions 异常,设置为true则替换为404异常
37 boolean decode404() default false;
38
39 // Feign客户端配置类,可以定制 Decoder、Encoder、Contract
40 /
*
41 * A custom configuration class for the feign client. Can contain override
42 * @Bean definition for the pieces that make up the client, for instance
43 * {@link feign.codec.Decoder}, {@link feign.codec.Encoder}, {@link feign.Contract}.
44 *
45 * @see FeignClientsConfiguration for the defaults
46 * @return list of configurations for feign client
47 /
48 Class<?>[] configuration() default {};
49
50 // FeignClient 接口的回调类,必须实现客户端接口,并注册为一个bean对象。
51 // 求失败或降级时就会进入回调方法中
52 /
*
53 * Fallback class for the specified Feign client interface. The fallback class must
54 * implement the interface annotated by this annotation and be a valid spring bean.
55 * @return fallback class for the specified Feign client interface
56 */
57 Class<?> fallback() default void.class;
58
59 // 回调类创建工厂
60 Class<?> fallbackFactory() default void.class;
61
62 // URL前缀
63 String path() default “”;
64
65 // 定义为 primary bean
66 boolean primary() default true;
67 }
复制代码
4、FeignClient 核心组件
从上面已经得知,FeignClient 的默认配置类为 FeignClientsConfiguration,这个类在 spring-cloud-openfeign-core 的 jar 包下,并且每个 FeignClient 都可以定义各自的配置类。

打开这个类,可以发现这个类注入了很多 Feign 相关的配置 Bean,包括 Retryer、FeignLoggerFactory、Decoder、Encoder、Contract 等,这些类在没有 Bean 被注入的情况下,会自动注入默认配置的 Bean。

View Code
这些其实就是 Feign 的核心组件了,对应的默认实现类如下。

如果想自定义这些配置,可增加一个配置类,然后配置到 @FeignClient 的 configuration 上。

① 先定义一个配置类

复制代码
1 public class ProducerFeignConfiguration {
2
3 @Bean
4 public Retryer feignRetryer() {
5 return new Retryer.Default();
6 }
7 }
复制代码
② 配置到 @FeignClient 中

1 @FeignClient(value = “demo-producer”, configuration = ProducerFeignConfiguration.class)
2 public interface ProducerFeignClient {
3
4 //…
5 }
5、Feign 属性文件配置
① 全局配置

前面已经了解到,@EnableFeignClients 的 defaultConfiguration 可以配置全局的默认配置bean对象。也可以使用 application.yml 文件来配置。

复制代码
1 feign:
2 client:
3 config:
4 # 默认全局配置
5 default:
6 connectTimeout: 1000
7 readTimeout: 1000
8 loggerLevel: basic
复制代码
② 指定客户端配置

@FeignClient 的 configuration 可以配置客户端特定的配置类,也可以使用 application.yml 配置。

复制代码
1 feign:
2 client:
3 config:
4 # 指定客户端名称
5 demo-producer:
6 # 连接超时时间
7 connectTimeout: 5000
8 # 读取超时时间
9 readTimeout: 5000
10 # Feign日志级别
11 loggerLevel: full
12 # Feign的错误解码器
13 errorDecoder: com.example.simpleErrorDecoder
14 # 配置拦截器
15 requestInterceptors:
16 - com.example.FooRequestInterceptor
17 - com.example.BarRequestInterceptor
18 # 404是否解码
19 decode404: false
20 #Feign的编码器
21 encoder: com.example.simpleEncoder
22 #Feign的解码器
23 decoder: com.example.simpleDecoder
24 #Feign的Contract配置
25 contract: com.example.simpleContract
复制代码
注意,如果通过Java代码的方式配置过 Feign,然后又通过属性文件的方式配置 Feign,属性文件中Feign的配置会覆盖Java代码的配置。但是可以配置 feign.client.default-to-properties=false 来改变Feign配置生效的优先级。

③ 开启压缩配置

Spring Cloud Feign支持对请求和响应进行GZIP压缩,以提高通信效率。

复制代码
1 feign:
2 compression:
3 request:
4 # 配置请求GZIP压缩
5 enabled: true
6 # 配置压缩支持的 MIME TYPE
7 mime-types: text/xml,application/xml,application/json
8 # 配置压缩数据大小的下限
9 min-request-size: 2048
10 response:
11 # 配置响应GZIP压缩
12 enabled: true
复制代码
6、FeignClient 开启日志
Feign 为每一个 FeignClient 都提供了一-个 feign.Logger 实例,可以在配置中开启日志。但是生产环境一般不要开启日志,因为接口调用可能会产生大量日志,一般在开发环境调试开启即可。

① 通过配置文件开启日志

首先设置客户端的 loggerLevel,然后配置 logging.level 日志级别为 debug。

复制代码
1 feign:
2 client:
3 config:
4 demo-producer:
5 # Feign日志级别
6 loggerLevel: full
7
8 logging:
9 level:
10 # 设置日志输出级别
11 com.lyyzoo.sunny.register.feign: debug
复制代码
之后调用 FeignClient 就可以看到接口调用日志了:

复制代码
1 2020-12-30 15:33:02.459 DEBUG 2720 — [nio-8020-exec-6] c.l.s.r.feign.ProducerFeignClient : [ProducerFeignClient#getUserById] —> GET http://demo-producer/v1/user/1?name=tom HTTP/1.1
2 2020-12-30 15:33:02.459 DEBUG 2720 — [nio-8020-exec-6] c.l.s.r.feign.ProducerFeignClient : [ProducerFeignClient#getUserById] —> END HTTP (0-byte body)
3 2020-12-30 15:33:02.462 DEBUG 2720 — [nio-8020-exec-6] c.l.s.r.feign.ProducerFeignClient : [ProducerFeignClient#getUserById] <— HTTP/1.1 200 (3ms)
4 2020-12-30 15:33:02.463 DEBUG 2720 — [nio-8020-exec-6] c.l.s.r.feign.ProducerFeignClient : [ProducerFeignClient#getUserById] connection: keep-alive
5 2020-12-30 15:33:02.463 DEBUG 2720 — [nio-8020-exec-6] c.l.s.r.feign.ProducerFeignClient : [ProducerFeignClient#getUserById] content-type: application/json
6 2020-12-30 15:33:02.463 DEBUG 2720 — [nio-8020-exec-6] c.l.s.r.feign.ProducerFeignClient : [ProducerFeignClient#getUserById] date: Wed, 30 Dec 2020 07:33:02 GMT
7 2020-12-30 15:33:02.463 DEBUG 2720 — [nio-8020-exec-6] c.l.s.r.feign.ProducerFeignClient : [ProducerFeignClient#getUserById] keep-alive: timeout=60
8 2020-12-30 15:33:02.463 DEBUG 2720 — [nio-8020-exec-6] c.l.s.r.feign.ProducerFeignClient : [ProducerFeignClient#getUserById] transfer-encoding: chunked
9 2020-12-30 15:33:02.463 DEBUG 2720 — [nio-8020-exec-6] c.l.s.r.feign.ProducerFeignClient : [ProducerFeignClient#getUserById]
10 2020-12-30 15:33:02.463 DEBUG 2720 — [nio-8020-exec-6] c.l.s.r.feign.ProducerFeignClient : [ProducerFeignClient#getUserById] {“id”:1,“name”:“tom”,“age”:10}
11 2020-12-30 15:33:02.463 DEBUG 2720 — [nio-8020-exec-6] c.l.s.r.feign.ProducerFeignClient : [ProducerFeignClient#getUserById] <— END HTTP (30-byte body)
12 2020-12-30 15:33:02.463 INFO 2720 — [nio-8020-exec-6] c.l.s.r.controller.FeignController : query user: User{id=1, name=‘tom’, age=10}
复制代码
② 通过Java代码开启日志

首先还是需要设置日志输出级别:

1 logging:
2 level:
3 # 设置日志输出级别
4 com.lyyzoo.sunny.register.feign: debug
然后配置一个 feign.Logger.Level 对象:

1 @Bean
2 public feign.Logger.Level loggerLevel() {
3 return Logger.Level.FULL;
4 }
③ Logger.Level

Logger.Level 的具体级别如下:

复制代码
1 public enum Level {
2 // 不打印任何日志
3 NONE,
4 // 只打印请求的方法和URL,以及响应状态码和执行时间
5 BASIC,
6 // 在BASIC的基础上,打印请求头和响应头信息
7 HEADERS,
8 // 记录所有请求与相应的明细,包含请求头、请求体、元数据
9 FULL
10 }
复制代码
回到顶部
二、扫描 @FeignClient 注解接口
Feign 是一个伪 Java HTTP 客户端,Feign 不做任何的请求处理,它只是简化API调用的开发,开发人员只需定义客户端接口,按照 springmvc 的风格开发声明式接口。然后在使用过程中我们只需要依赖注入Bean,然后调用对应的方法传递参数即可。

这里就有个问题,我们开发的是一个接口,然后使用 @FeignClient 注解标注,那又是如何能够注入这个接口的Bean对象的呢?其实很容易就能想到,一定是生成了接口的动态代理并注入到Spring容器中了,才能依赖注入这个客户端接口。这节就来看看 feign 是如何生成动态代理对象的。

1、FeignClient 动态注册组件 FeignClientsRegistrar
再看下 @EnableFeignClients 注解,它使用 @Import 导入了 FeignClientsRegistrar,FeignClient 注册者。从名字就可以看出,FeignClientsRegistrar 就是完成 FeignClient 注册的核心组件。

复制代码
1 @Retention(RetentionPolicy.RUNTIME)
2 @Target(ElementType.TYPE)
3 @Documented
4 // FeignClient 注册处理类
5 @Import(FeignClientsRegistrar.class)
6 public @interface EnableFeignClients {
7 //…
8 }
复制代码
FeignClientsRegistrar 实现了 ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware 三个接口。

ResourceLoaderAware 是为了注入资源加载器 ResourceLoader,EnvironmentAware 是为了注入当前环境组件 Environment,ImportBeanDefinitionRegistrar 是 Spring 动态注册 bean 的接口。

复制代码
1 class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
2
3 // patterned after Spring Integration IntegrationComponentScanRegistrar
4 // and RibbonClientsConfigurationRegistgrar
5
6 // 资源加载器
7 private ResourceLoader resourceLoader;
8 // 当前环境组件
9 private Environment environment;
10
11 //…
12 }
复制代码
ImportBeanDefinitionRegistrar 主要包含一个接口 registerBeanDefinitions,就是用来动态注册 BeanDefinition 的。平时我们一般就使用 @Service、@Component、@Bean 等注解向 Spring 容器注册对象,我们也可以实现 ImportBeanDefinitionRegistrar 接口来动态注册 BeanDefinition。

所有实现了 ImportBeanDefinitionRegistrar 接口的类的都会被 ConfigurationClassPostProcessor 处理,ConfigurationClassPostProcessor 实现了 BeanFactoryPostProcessor 接口,所以 ImportBeanDefinitionRegistrar 中动态注册的bean是优先于依赖它的bean初始化的,也能被aop、validator等机制处理。ImportBeanDefinitionRegistrar 实现类写好之后,还要使用 @Import 注解导入实现类。

复制代码
1 public interface ImportBeanDefinitionRegistrar {
2
3 /**
4 * Register bean definitions as necessary based on the given annotation metadata of
5 * the importing {@code @Configuration} class.
6 *

Note that {@link BeanDefinitionRegistryPostProcessor} types may not be
7 * registered here, due to lifecycle constraints related to {@code @Configuration}
8 * class processing.
9 *

The default implementation is empty.
10 * @param importingClassMetadata annotation metadata of the importing class
11 * @param registry current bean definition registry
12 */
13 default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
14 }
15
复制代码
BeanDefinition 又是什么呢?从注释可以了解到,BeanDefinition 就是用来描述 bean 实例的,BeanDefinition 包含了实例的属性值、构造函数参数等。其实就是通过这个 BeanDefinition 来获取实例对象。

复制代码
1 /**
2 * A BeanDefinition describes a bean instance, which has property values,
3 * constructor argument values, and further information supplied by
4 * concrete implementations.
5 *
6 *

This is just a minimal interface: The main intention is to allow a
7 * {@link BeanFactoryPostProcessor} to introspect and modify property values
8 * and other bean metadata.
9 */
10 public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {
11 }
复制代码
FeignClientsRegistrar 实现的 registerBeanDefinitions 方法中,主要有两步:

注册FeignClient默认配置对象,就是根据 @EnableFeignClients 的 defaultConfiguration 配置类注入默认配置,这个一般就是全局配置。
之后就是扫描 @FeignClient 注解的接口,封装成 BeanDefinition,然后用 BeanDefinitionRegistry 来注册。
因此,FeignClientsRegistrar 就是扫描 @FeignClient 注解的接口,并注册 FeignClient 的核心组件。

复制代码
1 // 根据注解元数据注册bean定义
2 @Override
3 public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
4 // 注册 FeignClient 默认配置类,根据 @EnableFeignClients 的 defaultConfiguration 注入默认配置
5 registerDefaultConfiguration(metadata, registry);
6 // 扫描 FeignClient 接口,注册 FeignClient
7 registerFeignClients(metadata, registry);
8 }
复制代码
2、扫描 @FeignClient 注解接口
接着看 registerFeignClients 方法,这个方法主要就是完成扫描 @FeignClient 注解的接口并完成 FeignClient 注册的工作。

主要的流程如下:

首先得到一个类路径扫描器 ClassPathScanningCandidateComponentProvider,就是用这个组件来扫描包路径获取到 @FeignClient 注解的接口。
如果 @EnableFeignClients 没有配置 clients 属性,扫描的包路径就是 @EnableFeignClients 配置的 value、basePackages、basePackageClasses 配置的包路径。并且根据注解过滤器来筛选有 @FeignClient 注解的接口。
如果 @EnableFeignClients 配置了 clients 属性,就只扫描 clients 配置的接口类。
之后就遍历扫描包路径,获取到 @FeignClient 注解的接口。可以看到 @FeignClient 注解的类型必须是一个接口,否则断言会抛出异常。
最后两步就是注册配置类和注册 FeignClient了,配置类就是 @FeignClient 的 configuration 属性配置的客户端配置类,这个配置类将覆盖 @EnableFeignClients 配置的全局配置类。
复制代码
1 **
2 * 注册 FeignClient
3 *
4 * @param metadata @EnableFeignClients 注解的元数据
5 * @param registry BeanDefinition 注册器
6 */
7 public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
8 // ClassPath 扫描器
9 ClassPathScanningCandidateComponentProvider scanner = getScanner();
10 scanner.setResourceLoader(this.resourceLoader);
11
12 Set basePackages;
13
14 // @EnableFeignClients 注解的属性
15 Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());
16 // 注解类型过滤器,过滤 @FeignClient 注解的接口
17 AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(FeignClient.class);
18 final Class<?>[] clients = attrs == null ? null : (Class<?>[]) attrs.get(“clients”);
19
20 // 如果 @EnableFeignClients 没有配置 clients,就取 value、basePackages、basePackageClasses 基础包
21 if (clients == null || clients.length == 0) {
22 // @FeignClient 注解过滤器
23 scanner.addIncludeFilter(annotationTypeFilter);
24 basePackages = getBasePackages(metadata);
25 }
26 // 如果 @EnableFeignClients 中配置了 clients
27 else {
28 final Set clientClasses = new HashSet<>();
29 basePackages = new HashSet<>();
30 for (Class<?> clazz : clients) {
31 // 基础包取配置的 client 类所在的包
32 basePackages.add(ClassUtils.getPackageName(clazz));
33 // 根据名称过滤
34 clientClasses.add(clazz.getCanonicalName());
35 }
36 // 类过滤器
37 AbstractClassTestingTypeFilter filter = new AbstractClassTestingTypeFilter() {
38 @Override
39 protected boolean match(ClassMetadata metadata) {
40 String cleaned = metadata.getClassName().replaceAll("\$", “.”);
41 // 根据名称过滤
42 return clientClasses.contains(cleaned);
43 }
44 };
45 // 必须类名在 clientClasses 中且类上有 @FeignClient 注解
46 scanner.addIncludeFilter(new AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));
47 }
48
49 // 扫描基础包
50 for (String basePackage : basePackages) {
51 Set candidateComponents = scanner.findCandidateComponents(basePackage);
52 for (BeanDefinition candidateComponent : candidateComponents) {
53 if (candidateComponent instanceof AnnotatedBeanDefinition) {
54 // verify annotated class is an interface
55 AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
56 AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
57 // @FeignClient 注解的类型必须是一个接口
58 Assert.isTrue(annotationMetadata.isInterface(),
59 “@FeignClient can only be specified on an interface”);
60
61 // @FeignClient 注解的属性
62 Map<String, Object> attributes = annotationMetadata
63 .getAnnotationAttributes(FeignClient.class.getCanonicalName());
64 // Feign 客户端名称,就是服务名
65 String name = getClientName(attributes);
66 // 注解客户端配置类
67 registerClientConfiguration(registry, name, attributes.get(“configuration”));
68 // 注册 FeignClient
69 registerFeignClient(registry, annotationMetadata, attributes);
70 }
71 }
72 }
73 }
复制代码
看下 getBasePackages 方法,可以看出,要扫描的包路径包含 @EnableFeignClients 配置的 value、basePackages、basePackageClasses 类所在的包,这里是取的多个配置的并集。

还有个需要注意的是,从最后一步可以看出,如果配置了 value、basePackages、basePackageClasses 时,就不会扫描 @EnableFeignClients 所在的包路径了,如果要扫描,需配置到 value 等属性中。

复制代码
1 protected Set getBasePackages(AnnotationMetadata importingClassMetadata) {
2 Map<String, Object> attributes = importingClassMetadata
3 .getAnnotationAttributes(EnableFeignClients.class.getCanonicalName());
4
5 Set basePackages = new HashSet<>();
6 // 先取 value
7 for (String pkg : (String[]) attributes.get(“value”)) {
8 if (StringUtils.hasText(pkg)) {
9 basePackages.add(pkg);
10 }
11 }
12 // 再取 basePackages
13 for (String pkg : (String[]) attributes.get(“basePackages”)) {
14 if (StringUtils.hasText(pkg)) {
15 basePackages.add(pkg);
16 }
17 }
18 // 再从 basePackageClasses 的 Class 获取包
19 for (Class<?> clazz : (Class[]) attributes.get(“basePackageClasses”)) {
20 basePackages.add(ClassUtils.getPackageName(clazz));
21 }
22
23 // 只有当没有配置 value、basePackages、basePackageClasses 时,才会扫描 @EnableFeignClients 所在的包路径
24 if (basePackages.isEmpty()) {
25 basePackages.add(ClassUtils.getPackageName(importingClassMetadata.getClassName()));
26 }
27 return basePackages;
28 }
复制代码
3、@FeignClient 接口构造 BeanDefinition 并注册
registerFeignClients 中扫描了包路径下的 @FeignCient 注解的接口,然后调用了 registerFeignClient 注册 FeignClient 接口的 BeanDefinition。

主要的流程如下:

首先创建了 BeanDefinitionBuilder,要构建的类型是 FeignClientFactoryBean,从名字可以看出就是创建 FeignClient 代理对象的工厂类。FeignClientFactoryBean 就是生成 FeignClient 接口动态代理的核心组件。
接着就是将 @FeignClient 注解的属性设置到 definition 中,它这里还设置了回调类 fallback 和回调工厂 fallbackFactory,但是有没有用呢?这个后面再分析。
然后是 bean 的名称,默认为 服务名称 + “FeignClient”,例如 “demo-consumerFeignClient”;如果设置了 qualifier 属性,名称就是 qualifier 设置的值。
之后用 BeanDefinitionBuilder 获取 BeanDefinition,并设置了对象类型为 FeignClient 接口的全限定名。
最后,将 BeanDefinition 等信息封装到 BeanDefinitionHolder,然后调用 BeanDefinitionReaderUtils.registerBeanDefinition 将 BeanDefinition 注册到Spring IoC 容器中。
复制代码
1 private void registerFeignClient(BeanDefinitionRegistry registry,
2 AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
3 String className = annotationMetadata.getClassName();
4 // FeignClientFactoryBean 就是用来生成 FeignClient 接口代理类的核心组件
5 BeanDefinitionBuilder definition = BeanDefinitionBuilder
6 .genericBeanDefinition(FeignClientFactoryBean.class);
7 validate(attributes);
8 // 从 @FeignClient 中得到的属性,并设置到 BeanDefinitionBuilder
9 definition.addPropertyValue(“url”, getUrl(attributes));
10 definition.addPropertyValue(“path”, getPath(attributes));
11 String name = getName(attributes);
12 definition.addPropertyValue(“name”, name);
13 String contextId = getContextId(attributes);
14 definition.addPropertyValue(“contextId”, contextId);
15 definition.addPropertyValue(“type”, className);
16 definition.addPropertyValue(“decode404”, attributes.get(“decode404”));
17 definition.addPropertyValue(“fallback”, attributes.get(“fallback”));
18 definition.addPropertyValue(“fallbackFactory”, attributes.get(“fallbackFactory”));
19 definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
20
21 // bean 的别名,demo-consumerFeignClient
22 String alias = contextId + “FeignClient”;
23 AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
24 // bean 的类型,就是 FeignClient 接口
25 beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className);
26
27 // has a default, won’t be null
28 boolean primary = (Boolean) attributes.get(“primary”);
29 beanDefinition.setPrimary(primary);
30
31 // 自定义的别名标识
32 String qualifier = getQualifier(attributes);
33 if (StringUtils.hasText(qualifier)) {
34 alias = qualifier;
35 }
36
37 // 将信息都封装到 BeanDefinitionHolder
38 BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, new String[] { alias });
39 // 注册bean
40 BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
41 }
复制代码
4、一张图总结 @FeignClient 接口扫描流程
下面用一张图来总结下 @FeignClient 接口是如何被扫描并注册到容器中的。

首先我们在代码中开发了 FeignClient 客户端调用接口,并用 @FeignClient 注解,注意 @FeignClient 只能加到接口上面。
之后我们需要在启动类或配置类中加一个 @EnableFeignClients 注解来启用 FeignClien。@EnableFeignClients 其实就是导入了 FeignClient 注册器 FeignClientsRegistrar。
FeignClientsRegistrar 实现了 ImportBeanDefinitionRegistrar 接口,在 registerBeanDefinitions 实现中,主要有两步:
注册全局配置配置类,就是 @EnableFeignClients 中指定的 defaultConfiguration
接着就是扫描注册 FeignClient
注册客户端时,先用 ClassPathScanningCandidateComponentProvider 扫描器扫描出配置的包下的 @FeignClient 注解的接口
扫描到 @FeignClient 接口后,先注册客户端特定的配置,就是 @FeignClient 配置的 configuration。
接着注册客户端:
先构建一个 BeanDefinitionBuilder,要创建的 BeanDefinition 类型是 FeignClientFactoryBean。
然后就是将 @FeignClient 中的配置设置到 BeanDefinitionBuilder,其实就是设置给 FeignClientFactoryBean。
之后解析出 FeignClient 的别名,默认是 服务名+“FeignClient”。
再用 BeanDefinitionBuilder 构建出 BeanDefinition,并将相关信息封装到 BeanDefinitionHolder 中。
最后使用 BeanDefinitionReaderUtils 完成 BeanDefinition 的注册。
将 BeanDefinition 注入容器后,就会调用 FeignClientFactoryBean 的 getObject 方法来创建动态代理。

回到顶部
三、构建 @FeignClient 接口动态代理
1、构造 FeignClient 的动态代理组件 FeignClientFactoryBean
FeignClientFactoryBean 这个组件就是生成 FeignClient 接口动态代理的组件。

FeignClientFactoryBean 实现了 FactoryBean 接口,当一个Bean实现了 FactoryBean 接口后,Spring 会先实例化这个工厂,然后调用 getObject() 创建真正的Bean。

1 class FeignClientFactoryBean implements FactoryBean, InitializingBean, ApplicationContextAware {
2
3 }
FeignClientFactoryBean 实现了 getObject 方法,它又调用了 getTarget 方法,getTarget 最后就创建了 FeignClient 接口的动态代理对象。

创建动态代理对象的主要流程如下:

首先获取了 Feign 上下文 FeignContext,FeignContext 跟 Ribbon 中 SpringClientFactory 是类似的,可以获取到每个服务的上下文。因为每个服务都有自己的配置、Encoder、Decoder 组件等,所以可以从 FeignContext 中获取到当前服务的组件。
然后从 FeignContext 中得到了 Feign.Builder,这个 Feign.Builder 就是最终用来创建动态代理对象的构造器。
@FeignClient 如果没有配置 url,就会通过服务名称构造带服务名的url地址,跟 RestTemplate 类似,最终肯定就是走负载均衡的请求;如果配置了 url,就是直接调用这个地址。
都会从 FeignContext 中获取一个 Client,如果配置了 url,就是获取 client 里的代理对象,并设置到 builder 中;否则就直接将 Client 设置到 builder。也就是说根据 url 判断是否使用负载均衡的 Client。
最终都会调用 Targeter 的 target 方法来构造动态代理对象,target 传入的参数包括当前的 FeignClientFactoryBean 对象、Feign.Builder、FeignContext,以及封装的 HardCodedTarget 对象。
复制代码
1 // 获取 FeignClient 代理对象的入口
2 @Override
3 public Object getObject() throws Exception {
4 return getTarget();
5 }
6
7 /**
8 * 创建一个 FeignClient 接口的代理对象,T 就是 @FeignClient 注解的接口类型
9 *
10 * @param the target type of the Feign client
11 * @return a {@link Feign} client created with the specified data and the context information
12 /
13 T getTarget() {
14 // Feign 上下文
15 FeignContext context = applicationContext.getBean(FeignContext.class);
16 // Feign 构造器
17 Feign.Builder builder = feign(context);
18
19 // 如果没有直接配置 url,就走负载均衡请求
20 if (!StringUtils.hasText(url)) {
21 if (!name.startsWith(“http”)) {
22 url = “http://” + name;
23 }
24 else {
25 url = name;
26 }
27 // 带服务名的地址 => http://demo-consumer
28 url += cleanPath();
29 // 返回的类型肯定是具备负载均衡能力的;HardCodedTarget => 硬编码的 Target
30 return (T) loadBalance(builder, context, new HardCodedTarget<>(type, name, url));
31 }
32
33 // 如果配置了 url,就直接请求 url 地址
34 if (StringUtils.hasText(url) && !url.startsWith(“http”)) {
35 url = “http://” + url;
36 }
37 String url = this.url + cleanPath();
38 // Client => Feign 发起 HTTP 调用的核心组件
39 Client client = getOptional(context, Client.class);
40 if (client != null) {
41 if (client instanceof LoadBalancerFeignClient) {
42 // 得到的是代理对象,就是原生的 Client.Default
43 client = ((LoadBalancerFeignClient) client).getDelegate();
44 }
45 if (client instanceof FeignBlockingLoadBalancerClient) {
46 // 得到的是代理对象,就是原生的 Client.Default
47 client = ((FeignBlockingLoadBalancerClient) client).getDelegate();
48 }
49 builder.client(client);
50 }
51 Targeter targeter = get(context, Targeter.class);
52 // targeter 创建动态代理对象
53 return (T) targeter.target(this, builder, context, new HardCodedTarget<>(type, name, url));
54 }
复制代码
复制代码
1 protected T loadBalance(Feign.Builder builder, FeignContext context, HardCodedTarget target) {
2 // 获取 Client
3 Client client = getOptional(context, Client.class);
4 if (client != null) {
5 builder.client(client);
6 // Targeter => HystrixTargeter
7 Targeter targeter = get(context, Targeter.class);
8 // targeter 创建动态代理对象
9 return targeter.target(this, builder, context, target);
10 }
11
12 throw new IllegalStateException(
13 “No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-netflix-ribbon?”);
14 }
复制代码
2、Feign 动态代理构造器 Feign.Builder
feign() 方法返回了 Feign.Builder,它也是从 FeignContext 中获取的,这个方法最重要的是设置了 Logger、Encoder、Decoder、Contract,并读取配置文件中 feign.client.
相关的配置。FeignClientsConfiguration 中配置了这几个接口的默认实现类,我们也可以自定义这几个实现类。

复制代码
1 protected Feign.Builder feign(FeignContext context) {
2 FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
3 Logger logger = loggerFactory.create(type);
4
5 // 我们可以定制 Logger、Encoder、Decoder、Contract
6 Feign.Builder builder = get(context, Feign.Builder.class)
7 // required values
8 .logger(logger)
9 .encoder(get(context, Encoder.class))
10 .decoder(get(context, Decoder.class))
11 .contract(get(context, Contract.class));
12 // @formatter:on
13
14 // 读取配置文件中 feign.client.* 的配置来配置 Feign
15 configureFeign(context, builder);
16
17 return builder;
18 }
复制代码
Feign.Builder 的默认实现是什么呢?从 FeignClientsConfiguration 中可以知道,默认情况下就是 Feign.Builder,如果启用了 feign.hystrix.enabled,那默认实现就是 HystrixFeign.Builder。

那 Feign.Builder 和 HystrixFeign.Build 有什么区别呢?对比下不难发现,主要区别就是创建动态代理的实现类 InvocationHandler 是不同的,在启用 hystrix 的情况下,会涉及到熔断、降级等,HystrixFeign.Build 也会设置 @FeignClient 配置的 fallback、fallbackFactory 降级配置类。这块等后面分析 hystrix 源码时再来看。现在只需要知道,feign 没有启用 hystrix,@FeignClient 配置的 fallback、fallbackFactory 降级回调是不生效的。

复制代码
1 public class FeignClientsConfiguration {
2
3 @Bean
4 @ConditionalOnMissingBean
5 public Retryer feignRetryer() {
6 // 从不重试
7 return Retryer.NEVER_RETRY;
8 }
9
10 @Bean
11 @Scope(“prototype”)
12 @ConditionalOnMissingBean
13 public Feign.Builder feignBuilder(Retryer retryer) {
14 // 默认为 Feign.Builder
15 return Feign.builder().retryer(retryer);
16 }
17
18 @Configuration(proxyBeanMethods = false)
19 @ConditionalOnClass({ HystrixCommand.class, HystrixFeign.class })
20 protected static class HystrixFeignConfiguration {
21
22 // 引入了 hystrix 并且,feign.hystrix.enabled = true
23 @Bean
24 @Scope(“prototype”)
25 @ConditionalOnMissingBean
26 @ConditionalOnProperty(name = “feign.hystrix.enabled”)
27 public Feign.Builder feignHystrixBuilder() {
28 // feign 启用 hystrix 后,Feign.Builder 就是 HystrixFeign.Builder
29 return HystrixFeign.builder();
30 }
31 }
32 }
复制代码
configureFeign 就是配置 Feign.Builder 的,从这个方法可以了解到,feign 配置生效的优先级。

Feign 有三块配置,一个是可以通过 Configuration 的方式配置,然后设置到 @FeignClient 的 configuration 参数;然后是全局的 feign.client.default 默认配置,以及服务特定的配置 feign.client.。

从 configureFeign 方法可以看出,默认情况下,优先级最低的是代码配置,其次是默认配置,最高优先级的是服务特定的配置。

如果想使代码配置优先级高于文件中的配置,可以设置 feign.client.defalut-to-properties=false 来改变 Feign 配置生效的优先级。

复制代码
1 protected void configureFeign(FeignContext context, Feign.Builder builder) {
2 // 配置文件中 feign.client.* 客户端配置
3 FeignClientProperties properties = applicationContext.getBean(FeignClientProperties.class);
4
5 FeignClientConfigurer feignClientConfigurer = getOptional(context, FeignClientConfigurer.class);
6 setInheritParentContext(feignClientConfigurer.inheritParentConfiguration());
7
8 if (properties != null && inheritParentContext) {
9 // defaultToProperties:优先使用配置文件中的配置
10 if (properties.isDefaultToProperties()) {
11 // 最低优先级:使用代码中的 Configuration 配置
12 configureUsingConfiguration(context, builder);
13 // 次优先级:使用 feign.client.default 默认配置
14 configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()), builder);
15 // 高优先级:使用 feign.client. 定义的配置
16 configureUsingProperties(properties.getConfig().get(contextId), builder);
17 }
18 // 优先使用Java代码的配置
19 else {
20 configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()), builder);
21 configureUsingProperties(properties.getConfig().get(contextId), builder);
22 configureUsingConfiguration(context, builder);
23 }
24 }
25 else {
26 configureUsingConfiguration(context, builder);
27 }
28 }
复制代码
3、Feign 网络调用组件 Client
Client 是 feign-core 中的组件,它只有一个接口 execute,这个接口就是调用 Request 的 url,然后将返回接口封装到 Response 中。

复制代码
1 public interface Client {
2
3 /**
4 * Executes a request against its {@link Request#url() url} and returns a response.
5 *
6 * @param request safe to replay.
7 * @param options options to apply to this request.
8 * @return connected response, {@link Response.Body} is absent or unread.
9 * @throws IOException on a network error connecting to {@link Request#url()}.
10 */
11 Response execute(Request request, Options options) throws IOException;
12 }
复制代码
Client 有如下的一些实现类:

Client 的自动化配置类是 FeignRibbonClientAutoConfiguration,FeignRibbonClientAutoConfiguration 导入了 HttpClient、OkHttp 以及默认的 Feign 负载均衡配置类。

复制代码
1 @ConditionalOnClass({ ILoadBalancer.class, Feign.class })
2 @ConditionalOnProperty(value = “spring.cloud.loadbalancer.ribbon.enabled”, matchIfMissing = true)
3 @Configuration(proxyBeanMethods = false)
4 @AutoConfigureBefore(FeignAutoConfiguration.class)
5 @EnableConfigurationProperties({ FeignHttpClientProperties.class })
6 @Import({ HttpClientFeignLoadBalancedConfiguration.class,
7 OkHttpFeignLoadBalancedConfiguration.class,
8 DefaultFeignLoadBalancedConfiguration.class })
9 public class FeignRibbonClientAutoConfiguration {
10 }
复制代码
① 启用 apache httpclient

从 HttpClientFeignLoadBalancedConfiguration 的配置可以看出,要启用 apache httpclient,需设置 feign.httpclient.enabled=true(默认为 true),并且需要加入了 feign-httpclient 的依赖(ApacheHttpClient)

启用 apache httpclient 后,LoadBalancerFeignClient 的代理对象就是 feign-httpclient 中的 ApacheHttpClient。

复制代码
1 @Configuration(proxyBeanMethods = false)
2 @ConditionalOnClass(ApacheHttpClient.class)
3 @ConditionalOnProperty(value = “feign.httpclient.enabled”, matchIfMissing = true)
4 @Import(HttpClientFeignConfiguration.class)
5 class HttpClientFeignLoadBalancedConfiguration {
6
7 @Bean
8 @ConditionalOnMissingBean(Client.class)
9 public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
10 SpringClientFactory clientFactory, HttpClient httpClient) {
11 ApacheHttpClient delegate = new ApacheHttpClient(httpClient);
12 return new LoadBalancerFeignClient(delegate, cachingFactory, clientFactory);
13 }
14
15 }
复制代码
② 启用 okhttp

从 OkHttpFeignLoadBalancedConfiguration 的配置可以看出,要启用 okhttp,需设置 feign.okhttp.enabled=true,且需要引入 feign-okhttp 的依赖(OkHttpClient)。

启用 okhttp 后,LoadBalancerFeignClient 的代理对象就是 feign-okhttp 的 OkHttpClient。

复制代码
1 @Configuration(proxyBeanMethods = false)
2 @ConditionalOnClass(OkHttpClient.class)
3 @ConditionalOnProperty(“feign.okhttp.enabled”)
4 @Import(OkHttpFeignConfiguration.class)
5 class OkHttpFeignLoadBalancedConfiguration {
6
7 @Bean
8 @ConditionalOnMissingBean(Client.class)
9 public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
10 SpringClientFactory clientFactory, okhttp3.OkHttpClient okHttpClient) {
11 OkHttpClient delegate = new OkHttpClient(okHttpClient);
12 return new LoadBalancerFeignClient(delegate, cachingFactory, clientFactory);
13 }
14
15 }
复制代码
③ 默认配置

没有引入 feign-httpclient 或者 feign-okhttp,就会走默认的 DefaultFeignLoadBalancedConfiguration。而默认的代理对象 Client.Default 其实就是使用 HttpURLConnection 发起 HTTP 调用。

复制代码
1 @Configuration(proxyBeanMethods = false)
2 class DefaultFeignLoadBalancedConfiguration {
3
4 @Bean
5 @ConditionalOnMissingBean
6 public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
7 SpringClientFactory clientFactory) {
8 return new LoadBalancerFeignClient(new Client.Default(null, null), cachingFactory,
9 clientFactory);
10 }
11
12 }
复制代码
可以看出,三个配置类创建的 Client 对象都是 LoadBalancerFeignClient,也就是支持负载均衡的请求。只是代理类不同,也就是最终发起 HTTP 调用的组件是不同的,默认配置下的代理类是 Client.Default,底层就是 HttpURLConnection。

这块其实跟分析 Ribbon 源码时,RestTemplate 的负载均衡是类似的。

4、动态代理目标器 Targeter
Targeter 接口只有一个接口方法,就是通过 target 方法获取动态代理对象。Targeter 有 DefaultTargeter、HystrixTargeter 两个实现类,

1 interface Targeter {
2
3 T target(FeignClientFactoryBean factory, Feign.Builder feign,
4 FeignContext context, Target.HardCodedTarget target);
5 }
在 FeignAutoConfiguration 配置类中可看到,只要引入了 HystrixFeign,Targeter 的默认实现就是 HystrixTargeter。

HystrixTargeter 一看就是用来整合 feign 和 hystrix 的,使 feign 调用可以实现熔断、限流、降级。

复制代码
1 public class FeignAutoConfiguration {
2
3 @Configuration(proxyBeanMethods = false)
4 @ConditionalOnClass(name = “feign.hystrix.HystrixFeign”)
5 protected static class HystrixFeignTargeterConfiguration {
6
7 @Bean
8 @ConditionalOnMissingBean
9 public Targeter feignTargeter() {
10 return new HystrixTargeter();
11 }
12
13 }
14
15 @Configuration(proxyBeanMethods = false)
16 @ConditionalOnMissingClass(“feign.hystrix.HystrixFeign”)
17 protected static class DefaultFeignTargeterConfiguration {
18
19 @Bean
20 @ConditionalOnMissingBean
21 public Targeter feignTargeter() {
22 return new DefaultTargeter();
23 }
24
25 }
26
27 }
复制代码
可以看到 HystrixTargeter 和 DefaultTargeter 的区别就在于 HystrixTargeter 会向 Feign.Builder 设置降级回调处理类,这样 feign 调用触发熔断、降级时,就可以进入回调类处理。

它们本质上最终来说都是调用 Feign.Builder 的 target 方法创建动态代理对象。

复制代码
1 class HystrixTargeter implements Targeter {
2
3 @Override
4 public T target(FeignClientFactoryBean factory, Feign.Builder feign,
5 FeignContext context, Target.HardCodedTarget target) {
6 if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) {
7 // 非 HystrixFeign.Builder 类型,就直接调用 target 方法
8 return feign.target(target);
9 }
10 // Feign 启用了 hystrix 后,就会向 HystrixFeign.Builder 设置回调类或回调工厂
11 feign.hystrix.HystrixFeign.Builder builder = (feign.hystrix.HystrixFeign.Builder) feign;
12 String name = StringUtils.isEmpty(factory.getContextId()) ? factory.getName() : factory.getContextId();
13 SetterFactory setterFactory = getOptional(name, context, SetterFactory.class);
14 if (setterFactory != null) {
15 builder.setterFactory(setterFactory);
16 }
17 Class<?> fallback = factory.getFallback();
18 // 设置回调类
19 if (fallback != void.class) {
20 return targetWithFallback(name, context, target, builder, fallback);
21 }
22 // 设置回调工厂类
23 Class<?> fallbackFactory = factory.getFallbackFactory();
24 if (fallbackFactory != void.class) {
25 return targetWithFallbackFactory(name, context, target, builder, fallbackFactory);
26 }
27
28 return feign.target(target);
29 }
30
31 }
复制代码
复制代码
1 class DefaultTargeter implements Targeter {
2
3 @Override
4 public T target(FeignClientFactoryBean factory, Feign.Builder feign,
5 FeignContext context, Target.HardCodedTarget target) {
6 return feign.target(target);
7 }
8 }
复制代码
5、Feign.Builder 创建动态代理
前面已经分析出,Feign.Builder 的默认实现就是 Feign.Builder,HystrixTargeter 中调用了 Feign.Builder 的 target 方法来创建动态代理。

target 方法中首先调用 build() 方法构建出 Feign,然后调用 Feign 的 newInstance 创建动态代理对象。
build() 方法中首先读取配置的 Client、Retryer、Logger、Contract、Encoder、Decoder 等对象。
然后获取了 InvocationHandlerFactory,默认就是 InvocationHandlerFactory.Default,这是 feign 提供的一个工厂类来创建代理对象 InvocationHandler。
接着创建了接口方法处理器工厂 SynchronousMethodHandler.Factor,它就是用来将接口方法封装成一个方法执行器 MethodHandler,默认实现类是 SynchronousMethodHandler。
还创建了 springmvc 注解处理器 ParseHandlersByName,可想而知,这就是用来处理接口中的 springmvc 注解的,将 REST 接口解析生成 MethodHandler。
最后创建了 Feign 对象,实现类是 ReflectiveFeign,之后就是使用 ReflectiveFeign 来创建动态代理对象了。
复制代码
1 public T target(Target target) {
2 return build().newInstance(target);
3 }
4
5 // 构建 Feign
6 public Feign build() {
7 // Feign Http调用客户端,默认为 Client.Default
8 Client client = Capability.enrich(this.client, capabilities);
9 // 重试器,默认是重不重试
10 Retryer retryer = Capability.enrich(this.retryer, capabilities);
11 // Feign 请求拦截器,可以对 Feign 请求模板RequestTemplate做一些定制化处理
12 List requestInterceptors = this.requestInterceptors.stream()
13 .map(ri -> Capability.enrich(ri, capabilities))
14 .collect(Collectors.toList());
15 // 日志组件,默认为 Slf4jLogger
16 Logger logger = Capability.enrich(this.logger, capabilities);
17 // 接口协议组件,默认为 SpringMvcContract
18 Contract contract = Capability.enrich(this.contract, capabilities);
19 // 配置类
20 Options options = Capability.enrich(this.options, capabilities);
21 // 编码器
22 Encoder encoder = Capability.enrich(this.encoder, capabilities);
23 // 解码器
24 Decoder decoder = Capability.enrich(this.decoder, capabilities);
25 // 创建 InvocationHandler 的工厂类
26 InvocationHandlerFactory invocationHandlerFactory =
27 Capability.enrich(this.invocationHandlerFactory, capabilities);
28 QueryMapEncoder queryMapEncoder = Capability.enrich(this.queryMapEncoder, capabilities);
29 // 接口方法处理器工厂
30 SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
31 new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
32 logLevel, decode404, closeAfterDecode, propagationPolicy, forceDecoding);
33 // 解析 springmvc 注解
34 ParseHandlersByName handlersByName =
35 new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,
36 errorDecoder, synchronousMethodHandlerFactory);
37 // ReflectiveFeign
38 return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
39 }
复制代码
InvocationHandlerFactory 包含一个 create 接口方法,默认实现是 InvocationHandlerFactory.Default,返回的 InvocationHandler 类型是 ReflectiveFeign.FeignInvocationHandler。

复制代码
1 package feign;
2
3 public interface InvocationHandlerFactory {
4
5 // 创建动态代理
6 InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch);
7
8 // 方法处理器
9 interface MethodHandler {
10
11 Object invoke(Object[] argv) throws Throwable;
12 }
13
14 static final class Default implements InvocationHandlerFactory {
15
16 @Override
17 public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) {
18 return new ReflectiveFeign.FeignInvocationHandler(target, dispatch);
19 }
20 }
21 }
复制代码
接着看 ReflectiveFeign 的 newInstance() 方法:

newInstance 的参数 target 就是前面封装的 Target.HardCodedTarget,它封装了客户端的类型、url 等属性。
首先是使用 ParseHandlersByName 将 FeignClient 接口中的接口转换成 MethodHandler,实际类型就是 SynchronousMethodHandler,这个细节就不在看了。
然后用 InvocationHandlerFactory 创建 InvocationHandler 代理对象,也就是 ReflectiveFeign.FeignInvocationHandler,调用动态代理对象的方法,最终都会进入到这个执行处理器里面。
最后,终于看到创建动态代理的地方了,使用 Proxy 创建了 FeignClient 的动态代理对象,这个动态代理的类型就是 @FeignClient 注解的接口的类型。最后被注入到 IoC 容器后,就可以在代码中注入自己编写的 FeignClient 客户端组件了。
最终就是通过 Proxy 创建一个实现了 FeignClient 接口的动态代理,然后所有接口方法的调用都会被 FeignInvocationHandler 拦截处理。

复制代码
1 public T newInstance(Target target) {
2 // 使用 ParseHandlersByName 将 FeignClient 接口中的接口转换成 MethodHandler,springmvc 注解由 Contract 组件处理
3 // MethodHandler => SynchronousMethodHandler
4 Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
5 Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
6 List defaultMethodHandlers = new LinkedList();
7
8 // 转换成 Method - MethodHandler 映射
9 for (Method method : target.type().getMethods()) {
10 if (method.getDeclaringClass() == Object.class) {
11 continue;
12 } else if (Util.isDefault(method)) {
13 DefaultMethodHandler handler = new DefaultMethodHandler(method);
14 defaultMethodHandlers.add(handler);
15 methodToHandler.put(method, handler);
16 } else {
17 methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
18 }
19 }
20 // 用 SynchronousMethodHandler.Factory 创建 SynchronousMethodHandler
21 InvocationHandler handler = factory.create(target, methodToHandler);
22 // 用 Proxy 创建动态代理,动态代理对象就是 SynchronousMethodHandler
23 T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
24 new Class<?>[] {target.type()}, handler);
25
26 for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
27 defaultMethodHandler.bindTo(proxy);
28 }
29 return proxy;
30 }
复制代码

6、一张图总结 FeignClient 生成动态代理的流程
下面用一张图来总结下生成 FeignClient 动态代理的流程:

首先 @EnableFeignClients 导入的注册器 FeignClientsRegistrar 会扫描 @FeignClient 注解的接口,并生成 FeingClientFactoryBean 的 BeanDefinition 注册到容器中。最后会调用 FeingClientFactoryBean 的 getObject 方法来获取接口的动态代理对象。
进入 FeingClientFactoryBean 的 getObject 方法,首先获取了 FeignContext,它其实就是每个客户端的容器,类似于一个 Map 结构,缓存了客户端与容器间的关系,后续大部分组件都是从 FeignContext 中获取。
从 FeignContext 中获取 Feign 构造器 Feign.Builder,并配置 Feign.Builder,配置来源有多个地方,优先级最高的是 application.yml 中的配置生效;也可以配置 feign.client.default-to-properties=false 设置Java代码配置为高优先级。
接下来就要根据 @FeignClient 是否配置了 url 决定是否走负载均衡的请求,其实就是设置的 Client 不一样:
如果配置了 url,表示一个具体的地址,就使用将 LoadBalancerFeignClient 的 delegate 作为 Client 设置给 Feign.Builder。
如果没有配置 url,表示通过服务名请求,就将 LoadBalancerFeignClient 作为 Client 设置给 Feign.Builder。
再从 FeignContext 中获取 Targeter,调用它的 target 方法来获取动态代理。
在 target 方法中,先调用 Feign.Builder 的 build() 方法构建了 ReflectiveFeign:
先是获取代理对象工厂 InvocationHandlerFactory,用于创建 InvocationHandler
然后用各个组件,构造了方法处理器工厂 SynchronousMethodHandler.Factory,接着创建了方法解析器 ParseHandlersByName
最后基于 InvocationHandlerFactory 和 ParseHandlersByName 构造了 ReflectiveFeign
最后调用 ReflectiveFeign 的 newInstance 方法反射创建接口的动态代理:
先用方法解析器 ParseHandlersByName 解析接口,将接口解析成 SynchronousMethodHandler
接着使用 InvocationHandlerFactory 创建了代理对象 InvocationHandler(ReflectiveFeign.FeignInvocationHandler)
最终用 Proxy 创建动态代理对象,对象的类型就是接口的类型,代理对象就是 ReflectiveFeign.FeignInvocationHandler。

回到顶部
四、FeignClient 结合Ribbon进行负载均衡请求
上一节已经分析出,最终在 Feign.Builder 的 build 方法构建了 ReflectiveFeign,然后利用 ReflectiveFeign 的 newInstance 方法创建了动态代理。这个动态代理的代理对象是 ReflectiveFeign.FeignInvocationHandler。最终来说肯定就会利用 Client 进行负载均衡的请求。这节就来看看 Feign 如果利用动态代理发起HTTP请求的。

1、FeignClient 动态代理请求
使用 FeignClient 接口时,注入的其实是动态代理对象,调用接口方法时就会进入执行器 ReflectiveFeign.FeignInvocationHandler,从 FeignInvocationHandler 的 invoke 方法可以看出,就是根据 method 获取要执行的方法处理器 MethodHandler,然后执行方法。MethodHandler 的实际类型就是 SynchronousMethodHandler。

复制代码
1 static class FeignInvocationHandler implements InvocationHandler {
2 private final Target target;
3 private final Map<Method, MethodHandler> dispatch;
4
5 FeignInvocationHandler(Target target, Map<Method, MethodHandler> dispatch) {
6 this.target = checkNotNull(target, “target”);
7 this.dispatch = checkNotNull(dispatch, “dispatch for %s”, target);
8 }
9
10 @Override
11 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
12 //…
13 // 根据 method 获取 MethodHandler,然后执行方法
14 return dispatch.get(method).invoke(args);
15 }
16 }
复制代码
接着看 SynchronousMethodHandler 的 invoke 方法,核心逻辑就两步:

先根据请求参数构建请求模板 RequestTemplate,就是处理 URI 模板、参数,比如替换掉 uri 中的占位符、拼接参数等。
然后调用了 executeAndDecode 执行请求,并将相应结果解码返回。
复制代码
1 public Object invoke(Object[] argv) throws Throwable {
2 // 构建请求模板,例如有 url 参数,请求参数之类的
3 RequestTemplate template = buildTemplateFromArgs.create(argv);
4 Options options = findOptions(argv);
5 Retryer retryer = this.retryer.clone();
6 while (true) {
7 try {
8 // 执行并解码
9 return executeAndDecode(template, options);
10 } catch (RetryableException e) {
11 // 重试,默认是从不重试
12 try {
13 retryer.continueOrPropagate(e);
14 } catch (RetryableException th) {
15 Throwable cause = th.getCause();
16 if (propagationPolicy == UNWRAP && cause != null) {
17 throw cause;
18 } else {
19 throw th;
20 }
21 }
22 if (logLevel != Logger.Level.NONE) {
23 logger.logRetry(metadata.configKey(), logLevel);
24 }
25 continue;
26 }
27 }
28 }
复制代码
可以看到,经过处理后,URI 上的占位符就被参数替换了,并且拼接了请求参数。

2、执行请求 executeAndDecode
接着看 executeAndDecode,主要有三步:

先调用 targetRequest 方法,主要就是遍历 RequestInterceptor 对请求模板 RequestTemplate 定制化,然后调用 HardCodedTarget 的 target 方法将 RequestTemplate 转换成 Request 请求对象,Request 封装了请求地址、请求头、body 等信息。
然后使用客户端 client 来执行请求,就是 LoadBalancerFeignClient,这里就进入了负载均衡请求了。
最后用解码器 decoder 来解析响应结果,将结果转换成接口的返回类型。
复制代码
1 Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
2 // 处理RequestTemplate,得到请求对象 Request
3 Request request = targetRequest(template);
4
5 Response response;
6 try {
7 // 调用 client 执行请求,client => LoadBalancerFeignClient
8 response = client.execute(request, options);
9 // 构建响应 Response
10 response = response.toBuilder()
11 .request(request)
12 .requestTemplate(template)
13 .build();
14 } catch (IOException e) {
15 //…
16 }
17
18 if (decoder != null) {
19 // 使用解码器解码,将返回数据转换成接口的返回类型
20 return decoder.decode(response, metadata.returnType());
21 }
22
23 //…
24 }
25 // 应用拦截器处理 RequestTemplate,最后使用 target 从 RequestTemplate 中得到 Request
26 Request targetRequest(RequestTemplate template) {
27 for (RequestInterceptor interceptor : requestInterceptors) {
28 interceptor.apply(template);
29 }
30 // target => HardCodedTarget
31 return target.apply(template);
32 }
复制代码
HardCodedTarget 是硬编码写死的,我们没有办法定制化,看下它的 apply 方法,主要就是处理 RequestTemplate 模板的地址,生成完成的请求地址。最后返回 Request 请求对象。

复制代码
1 public Request apply(RequestTemplate input) {
2 if (input.url().indexOf(“http”) != 0) {
3 // url() => http://demo-producer
4 // input.target 处理请求模板
5 input.target(url());
6 }
7 return input.request();
8 }
复制代码
可以看到经过 HardCodedTarget 的 apply 方法之后,就拼接上了 url 前缀了。

3、LoadBalancerFeignClient 负载均衡
LoadBalancerFeignClient 是 Feign 实现负载均衡核心的组件,是 Feign 网络请求组件 Client 的默认实现,LoadBalancerFeignClient 最后是使用 FeignLoadBalancer 来进行负载均衡的请求。

看 LoadBalancerFeignClient 的 execute 方法,从这里到后面执行负载均衡请求,其实跟分析 Ribbon 源码中 RestTemplate 的负载均衡请求都是类似的了。

可以看到也是先将请求封装到 ClientRequest,实现类是 FeignLoadBalancer.RibbonRequest。注意 RibbonRequest 第一个参数 Client 就是设置的 LoadBalancerFeignClient 的代理对象,启用 apache httpclient 时,就是 ApacheHttpClient。
然后获取客户端配置,也就是说 Ribbon 的客户端配置对 Feign 通用生效。
最后获取了负载均衡器 FeignLoadBalancer,然后执行负载均衡请求。
复制代码
1 public Response execute(Request request, Request.Options options) throws IOException {
2 try {
3 URI asUri = URI.create(request.url());
4 // 客户端名称:demo-producer
5 String clientName = asUri.getHost();
6 URI uriWithoutHost = cleanUrl(request.url(), clientName);
7 // 封装 ClientRequest => FeignLoadBalancer.RibbonRequest
8 FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(
9 this.delegate, request, uriWithoutHost);
10 // 客户端负载均衡配置 ribbon.demo-producer.*
11 IClientConfig requestConfig = getClientConfig(options, clientName);
12 // lbClient => 负载均衡器 FeignLoadBalancer,执行负载均衡请求
13 return lbClient(clientName)
14 .executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse();
15 }
16 catch (ClientException e) {
17 //…
18 }
19 }
20
21 private FeignLoadBalancer lbClient(String clientName) {
22 return this.lbClientFactory.create(clientName);
23 }
复制代码
进入 executeWithLoadBalancer 方法,这就跟 Ribbon 源码中分析的是一样的了,最终就验证了 Feign 基于 Ribbon 来做负载均衡请求。

复制代码
1 public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {
2 // 负载均衡器执行命令
3 LoadBalancerCommand command = buildLoadBalancerCommand(request, requestConfig);
4
5 try {
6 return command.submit(
7 new ServerOperation() {
8 @Override
9 public Observable call(Server server) {
10 // 用Server的信息重构URI地址
11 URI finalUri = reconstructURIWithServer(server, request.getUri());
12 S requestForServer = (S) request.replaceUri(finalUri);
13 try {
14 // 实际调用 LoadBalancerFeignClient 的 execute 方法
15 return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
16 }
17 catch (Exception e) {
18 return Observable.error(e);
19 }
20 }
21 })
22 .toBlocking()
23 .single();
24 } catch (Exception e) {
25 //…
26 }
27 }
复制代码
重构URI后,实际是调用 FeignLoadBalancer 的 execute 方法来执行最终的HTTP调用的。看下 FeignLoadBalancer 的 execute 方法,最终来说,就是使用代理的HTTP客户端来执行请求。

默认情况下,就是 Client.Default,用 HttpURLConnection 执行HTTP请求;启用了 httpclient 后,就是 ApacheHttpClient;启用了 okhttp,就是 OkHttpClient。

这里有一点需要注意的是,FeignClient 虽然可以配置超时时间,但进入 FeignLoadBalancer 的 execute 方法后,可以看到会用 Ribbon 的超时时间覆盖 Feign 配置的超时时间,最终以 Ribbon 的超时时间为准。

复制代码
1 public RibbonResponse execute(RibbonRequest request, IClientConfig configOverride) throws IOException {
2 Request.Options options;
3 if (configOverride != null) {
4 // 用 Ribbon 的超时时间覆盖了feign配置的超时时间
5 RibbonProperties override = RibbonProperties.from(configOverride);
6 options = new Request.Options(override.connectTimeout(this.connectTimeout),
7 override.readTimeout(this.readTimeout));
8 }
9 else {
10 options = new Request.Options(this.connectTimeout, this.readTimeout);
11 }
12 // request.client() HTTP客户端对象
13 Response response = request.client().execute(request.toRequest(), options);
14 return new RibbonResponse(request.getUri(), response);
15 }
复制代码
4、一张图总结 Feign 负载均衡请求
关于Ribbon的源码分析请看前面 Ribbon 相关的两篇文章,Ribbon 如何从 eureka 注册中心获取 Server 就不再分析了。

下面这张图总结了 Feign 负载均衡请求的流程:

首先服务启动的时候会扫描解析 @FeignClient 注解的接口,并生成代理类注入到容器中。我们注入 @FeignClient 接口时其实就是注入的这个代理类。
调用接口方法时,会被代理对象拦截,进入 ReflectiveFeign.FeignInvocationHandler 的 invoke 方法执行请求。
FeignInvocationHandler 会根据调用的接口方法获取已经构建好的方法处理器 SynchronousMethodHandler,然后调用它的 invoke 方法执行请求。
在 SynchronousMethodHandler 的 invoke 方法中,会先根据请求参数构建请求模板 RequestTemplate,这个时候会处理参数中的占位符、拼接请求参数、处理body中的参数等等。
然后将 RequestTemplate 转成 Request,在转换的过程中:
先是用 RequestInterceptor 处理请求模板,因此我们可以自定义拦截器来定制化 RequestTemplate。
之后用 Target(HardCodedTarget)处理请求地址,拼接上服务名前缀。
最后调用 RequestTemplate 的 request 方法获取到 Request 对象。
得到 Request 后,就调用 LoadBalancerFeignClient 的 execute 方法来执行请求并得到请求结果 Response:
先构造 ClientRequest,并获取到负载均衡器 FeignLoadBalancer,然后就执行负载均衡请求。
负载均衡请求最终进入到 AbstractLoadBalancerAwareClient,executeWithLoadBalancer 方法中,会先构建一个 LoadBalancerCommand,然后提交一个 ServerOperation。
LoadBalancerCommand 会通过 LoadBalancerContext 根据服务名获取一个 Server。
在 ServerOperation 中根据 Server 的信息重构URI,将服务名替换为具体的IP地址,之后就可以发起真正的HTTP调用了。
HTTP调用时,底层使用的组件默认是 HttpURLConnection;启用了okhttp,就是 okhttp 的 OkHttpClient;启用了 httpclient,就是 apache 的 HttpClient。
最红用 HTTP 客户端组件执行请求,得到响应结果 Response。
得到 Response 后,就使用解码器 Decoder 解析响应结果,返回接口方法定义的返回类型。

负载均衡获取Server的核心组件是 LoadBalancerClient,具体的源码分析可以参考 Ribbon 源码分析的两篇文章。LoadBalancerClient 负载均衡的原理可以看下面这张图。

作者:bojiangzhou
出处:http://www.cnblogs.com/chiangchou/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
分类: springcloud
标签: 动态代理, ribbon, feign, @FeignClient
好文要顶 关注我 收藏该文
bojiangzhou
关注 - 7
粉丝 - 679
+加关注
00
« 上一篇: SpringCloud 源码系列(5)—— 负载均衡 Ribbon(下)
posted on 2021-01-08 01:42 bojiangzhou 阅读(32) 评论(0) 编辑 收藏
刷新评论刷新页面返回顶部
登录后才能发表评论,立即 登录 或 注册, 访问 网站首页
【推荐】News: 大型组态、工控、仿真、CADGIS 50万行VC++源码免费下载
【推荐】有你助力,更好为你——博客园用户消费观调查,附带小惊喜!
【推荐】AWS携手博客园为开发者送福利,注册立享12个月免费套餐
【推荐】第一个NoSQL数据库,在大规模和一致性之间找到了平衡
【推荐】七牛云新老用户同享 1 分钱抢 CDN 1TB流量大礼包!
【推荐】了不起的开发者,挡不住的华为,园子里的品牌专区
【推荐】未知数的距离,毫秒间的传递,声网与你实时互动

相关博文:
· SpringCloudFeign参数问题
· springcloudfeign增加熔断器Hystrix
· SpringCloud应用间通信-RestTemplate与Feign
· SpringCloud学习系列-Feign负载均衡(2)
· springcloud核心组件Eureka、Ribbon、Feign、Hystrix、Zuul
» 更多推荐…

最新 IT 新闻:
· 2025年中国大陆芯片将达2230亿美元 但自给率仍不到20%?
· 法拉第未来任命新CFO!贾跃亭激动发声
· 瑞幸咖啡“宫斗大戏”背后:陆正耀还想当王
· Git服务器配置错误导致日产汽车源码在网上泄露
· “996”“007”加班文化再引热议 谁来保护职场“打工人”权益?
» 更多新闻…
公告
昵称: bojiangzhou
园龄: 5年
粉丝: 679
关注: 7
+加关注
搜索

积分与排名
积分 - 128064
排名 - 6472
随笔分类 (55)
dbutils(1)
devops(1)
easyui(2)
ExtJs(1)
fastdfs(1)
hibernate(1)
IDE(1)
java(9)
JavaScript(6)
jvm(4)
mybatis(1)
mysql(7)
redis(3)
spring(1)
spring boot(2)
更多
最新评论

  1. Re:毕业设计之进销存管理系统 —— 一步步搭建自己的框架及系统
    非常感谢博主,相比博主真感惭愧,我能仿做一个吗?

–稻香的落脚地
2. Re:学生成绩管理系统/学生信息管理系统
@佳世俪人 私信我…
–bojiangzhou
3. Re:学生成绩管理系统/学生信息管理系统
你好,博主博客里的百度云链接失效了,方便的话,可以请你发一下成绩管理系统的源码吗?
我的qq是2059643724

–佳世俪人
4. Re:学生成绩管理系统/学生信息管理系统
@超级小白龙 你好,博主博客里的百度云链接失效了,方便的话可以请你发一下成绩管理系统的源码吗? 我的qq是1295017889…
–如何一叶知秋
5. Re:学生成绩管理系统/学生信息管理系统
大佬有整体源码吗?有就发一下,不胜感激!184358891@qq.com

–胖头鱼6666
阅读排行榜

  1. jQuery jsonp跨域请求(257560)
  2. 用FastDFS一步步搭建文件管理系统(209307)
  3. 在Intellij IDEA中使用Debug(198122)
  4. 学生成绩管理系统/学生信息管理系统(155671)
  5. 基于SpringBoot搭建应用开发框架(一) —— 基础架构(75403)
  6. easyui-textbox 和 easyui-validatebox 设置值和获取值(62852)
  7. 毕业设计之进销存管理系统 —— 一步步搭建自己的框架及系统(40873)
  8. hibernate基于注解的维护权反转:@OneToMany(mappedBy=)(38234)
  9. MySql学习(三) —— 子查询(where、from、exists) 及 连接查询(left join、right join、inner join、union join)(36218)
  10. JavaScript 面向对象(一) —— 基础篇(33110)
    评论排行榜
  11. 学生成绩管理系统/学生信息管理系统(89)
  12. 毕业设计之进销存管理系统 —— 一步步搭建自己的框架及系统(67)
  13. 基于SpringBoot搭建应用开发框架(一) —— 基础架构(63)
  14. 用FastDFS一步步搭建文件管理系统(45)
  15. 在Intellij IDEA中使用Debug(35)
  16. jQuery jsonp跨域请求(31)
  17. 基于SpringBoot搭建应用开发框架(二) —— 登录认证(15)
  18. 跟着视频做的SSH项目总结(11)
  19. 手机进销存系统/供应链管理系统(9)
  20. 基于 Javassist 和 Javaagent 实现动态切面(8)
    推荐排行榜
  21. 在Intellij IDEA中使用Debug(100)
  22. jQuery jsonp跨域请求(100)
  23. 用FastDFS一步步搭建文件管理系统(65)
  24. 基于SpringBoot搭建应用开发框架(二) —— 登录认证(61)
  25. 基于SpringBoot搭建应用开发框架(一) —— 基础架构(61)
    Copyright © 2021 bojiangzhou
    Powered by .NET 5.0 on Kubernetes
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值