ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

高邮

2021-01-08 18:01:56  阅读:256  来源: 互联网

标签:FeignClient Feign 高邮 配置 feign 接口 class


目录

 


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地址:

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。

 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      * <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 }
复制代码

标签:FeignClient,Feign,高邮,配置,feign,接口,class
来源: https://www.cnblogs.com/SHAO1994/p/14252724.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有