VB.net 2010 视频教程 VB.net 2010 视频教程 python基础视频教程
SQL Server 2008 视频教程 c#入门经典教程 Visual Basic从门到精通视频教程
当前位置:
首页 > 编程开发 > Java教程 >
  • 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 <dependency>
2     <groupId>org.springframework.cloud</groupId>
3     <artifactId>spring-cloud-starter-openfeign</artifactId>
4 </dependency>

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<User> getUserById(@PathVariable Long id, @RequestParam(required = false) String name);
 6 
 7     @PostMapping("/v1/user")
 8     ResponseEntity<User> 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<User> queryUser() {
10         ResponseEntity<User> 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<User> createUser() {
18         ResponseEntity<User> 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<User> 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<User> 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} <code>@FeignClient</code>).
 6  * Configures component scanning directives for use with
 7  * {@link org.springframework.context.annotation.Configuration}
 8  * <code>@Configuration</code> 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 <code>@Configuration</code> for all feign clients. Can contain override
28      * <code>@Bean</code> 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 <code>@RibbonClient</code> 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      * <code>@Bean</code> 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。

复制代码
 1 package org.springframework.cloud.openfeign;
 2 
 3 @Configuration(proxyBeanMethods = false)
 4 public class FeignClientsConfiguration {
 5     @Autowired
 6     private ObjectFactory<HttpMessageConverters> messageConverters;
 7     @Autowired(required = false)
 8     private List<AnnotatedParameterProcessor> parameterProcessors = new ArrayList<>();
 9     @Autowired(required = false)
10     private List<FeignFormatterRegistrar> feignFormatterRegistrars = new ArrayList<>();
11     @Autowired(required = false)
12     private Logger logger;
13 
14     @Bean
15     @ConditionalOnMissingBean
16     public Decoder feignDecoder() {
17         return new OptionalDecoder(new ResponseEntityDecoder(new SpringDecoder(this.messageConverters)));
18     }
19 
20     @Bean
21     @ConditionalOnMissingBean
22     @ConditionalOnMissingClass("org.springframework.data.domain.Pageable")
23     public Encoder feignEncoder(ObjectProvider<AbstractFormWriter> formWriterProvider) {
24         return springEncoder(formWriterProvider);
25     }
26 
27     @Bean
28     @ConditionalOnClass(name = "org.springframework.data.domain.Pageable")
29     @ConditionalOnMissingBean
30     public Encoder feignEncoderPageable(
31             ObjectProvider<AbstractFormWriter> formWriterProvider) {
32         //...
33         return encoder;
34     }
35 
36     @Bean
37     @ConditionalOnMissingBean
38     public Contract feignContract(ConversionService feignConversionService) {
39         return new SpringMvcContract(this.parameterProcessors, feignConversionService);
40     }
41 
42     @Bean
43     @ConditionalOnMissingBean
44     public Retryer feignRetryer() {
45         return Retryer.NEVER_RETRY;
46     }
47 
48     @Bean
49     @Scope("prototype")
50     @ConditionalOnMissingBean
51     public Feign.Builder feignBuilder(Retryer retryer) {
52         return Feign.builder().retryer(retryer);
53     }
54 
55     @Bean
56     @ConditionalOnMissingBean(FeignLoggerFactory.class)
57     public FeignLoggerFactory feignLoggerFactory() {
58         return new DefaultFeignLoggerFactory(this.logger);
59     }
60     
61     @Configuration(proxyBeanMethods = false)
62     @ConditionalOnClass({ HystrixCommand.class, HystrixFeign.class })
63     protected static class HystrixFeignConfiguration {
64         @Bean
65         @Scope("prototype")
66         @ConditionalOnMissingBean
67         @ConditionalOnProperty(name = "feign.hystrix.enabled")
68         public Feign.Builder feignHystrixBuilder() {
69             return HystrixFeign.builder();
70         }
71 
72     }
73 
74     //...
75 }
复制代码

这些其实就是 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      * <p>Note that {@link BeanDefinitionRegistryPostProcessor} types may <em>not</em> be
 7      * registered here, due to lifecycle constraints related to {@code @Configuration}
 8      * class processing.
 9      * <p>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  * <p>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<String> 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<String> 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<BeanDefinition> 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<String> getBasePackages(AnnotationMetadata importingClassMetadata) {
 2     Map<String, Object> attributes = importingClassMetadata
 3             .getAnnotationAttributes(EnableFeignClients.class.getCanonicalName());
 4 
 5     Set<String> 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<Object>, 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 <T> 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> 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> T loadBalance(Feign.Builder builder, FeignContext context, HardCodedTarget<T> 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.<clientName>。

从 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.<clientName> 定义的配置
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> T target(FeignClientFactoryBean factory, Feign.Builder feign,
4             FeignContext context, Target.HardCodedTarget<T> 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> T target(FeignClientFactoryBean factory, Feign.Builder feign,
 5                         FeignContext context, Target.HardCodedTarget<T> 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> T target(FeignClientFactoryBean factory, Feign.Builder feign,
5             FeignContext context, Target.HardCodedTarget<T> 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> T target(Target<T> 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<RequestInterceptor> 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> T newInstance(Target<T> 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<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();
 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<T> command = buildLoadBalancerCommand(request, requestConfig);
 4 
 5     try {
 6         return command.submit(
 7             new ServerOperation<T>() {
 8                 @Override
 9                 public Observable<T> 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/


相关教程