一、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 负载均衡的原理可以看下面这张图。