经过前面几篇的源码分析,我们对 OpenFeign
与 Ribbon
也相对熟悉了。
看框架源码的目的就是解决我们的一些疑惑,能够知其然并知其所以然,以及用好框架。
很多时候,我们需要在项目中调用一些第三方接口,例如对接支付宝支付、微信支付,调用支付接口。如果项目中引入了 OpenFeign
,那么我们是否可以使用 OpenFeign
去调用第三方接口呢?答案肯定是可以的。
虽然调用第三方接口不需要服务发现,所以也不需要使用 Ribbon
实现负载均衡,但我们依然可以单独使用 OpenFeign
。使用 OpenFeign
不仅能够简化调用接口的步骤,也能顺便使用 OpenFeign
提供的重试机制,不需要再编写一个 HttpUtils
工具类,何乐而不为呢。
本篇内容:
OpenFeign
使用 OkHttp
OpenFeign
的重试配置 OpenFeign
的拦截器配置
OpenFeign
通过 Client
发送 http
请求,而默认的 Client
则是使用 HttpURLConnection
实现发送 http
请求的。
如果你觉得 HttpURLConnection
性能不行,你也可以通过自定义 Client
将发送 http
请求的动作切换到其它你认为更优秀的框架来完成。 OpenFeign
也为我们提供了两种选择,一种是使用 okhttp
框架,另一种是使用 apache
的 httpclient
框架。
当 OpenFeign
与 Ribbon
双剑合璧时,实际向服务提供者发起请求还是由 OpenFeign
的 Client
完成,所以我们切换 Client
是全局有效的。
OpenFeign
为我们使用 okhttp
框架提供了 Client
接口的实现( feign.okhttp.OkHttpClient
),
并且提供自动配置类 FeignAutoConfiguration.OkHttpFeignConfiguration
。
public class FeignAutoConfiguration { @Configuration(proxyBeanMethods = false) @ConditionalOnClass(OkHttpClient.class) @ConditionalOnMissingClass("com.netflix.loadbalancer.ILoadBalancer") @ConditionalOnMissingBean(okhttp3.OkHttpClient.class) @ConditionalOnProperty("feign.okhttp.enabled") protected static class OkHttpFeignConfiguration { @Bean @ConditionalOnMissingBean(Client.class) public Client feignClient(okhttp3.OkHttpClient client) { return new OkHttpClient(client); } } } 复制代码
该配置类生效的前提条件很多:
feign-okhttp
包,即当前项目的 classpath
下存在一个 feign.okhttp.OkHttpClient
类,该类实现了 Client
接口; <dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-okhttp</artifactId> <version>11.0</version> </dependency> 复制代码
application.yaml
中配置 feign.okhttp.enabled
为 true
; ## 配置feign使用okhttp feign: okhttp: enabled: true 复制代码
Ribbon
包,即不使用 Ribbon
,项目中不存在 com.netflix.loadbalancer.ILoadBalancer
这个类;
而当项目中使用 Ribbon
时, OpenFeign
创建的不再是默认的 Default
,也不是 OkHttpClient
,而是 LoadBalancerFeignClient
。 FeignRibbonClientAutoConfiguration
配置类被设置在 FeignAutoConfiguration
配置类之前完成自动配置, FeignRibbonClientAutoConfiguration
往容器中注入了 LoadBalancerFeignClient
。
@AutoConfigureBefore(FeignAutoConfiguration.class) @Import({ HttpClientFeignLoadBalancedConfiguration.class, OkHttpFeignLoadBalancedConfiguration.class, DefaultFeignLoadBalancedConfiguration.class }) public class FeignRibbonClientAutoConfiguration { } 复制代码
FeignRibbonClientAutoConfiguration
使用 @Import
导入三个自动配置 LoadBalancerFeignClient
的配置类,但最终只会有一个被导入,当我们配置 feign.okhttp.enabled
为 true
,且项目中添加了 feign-okhttp
包的依赖时, OkHttpFeignLoadBalancedConfiguration
生效。
@ConditionalOnProperty("feign.okhttp.enabled") // 导入OkHttpFeignConfiguration自动配置类 @Import(OkHttpFeignConfiguration.class) class OkHttpFeignLoadBalancedConfiguration { @Bean @ConditionalOnMissingBean(Client.class) public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory, SpringClientFactory clientFactory, // 该okHttpClient是由OkHttpFeignConfiguration自动配置的 okhttp3.OkHttpClient okHttpClient) { OkHttpClient delegate = new OkHttpClient(okHttpClient); return new LoadBalancerFeignClient(delegate, cachingFactory, clientFactory); } } 复制代码
虽然 OpenFeign
与 Ribbon
整合使用时, OpenFeign
使用的 Client
是 LoadBalancerFeignClient
,但这个 LoadBalancerFeignClient
只是实现负载均衡的桥梁,实际还是通过委托模式将发送请求的工作委托给其它 Client
完成,而这里使用的就是 feign.okhttp.OkHttpClient
。
通过前面的源码分析的学习,我们知道,当配置 @FeignClient
注解的 url
属性时,不会使用 LoadBalancerFeignClient
,但我们配置的 feign.okhttp.OkHttpClient
依然生效。原因是 OpenFeign
不会再创建新的 Client
,但会从 LoadBalancerFeignClient
对象中取得委托对象 feign.okhttp.OkHttpClient
。
我们也可以在 FeignClientFactoryBean
的 getTarget
方法添加断点调试,以验证使用 @FeignClient
注解注释的第三方接口在不走服务发现的情况下,会不会使用 feign.okhttp.OkHttpClient
。测试省略...
当我们使用 OpenFeign
调用第三方接口时,由于第三方接口不走服务发现,所以我们需要直接在 @FeignClient
注解上给出接口的 url
。由于在 @FeignClient
注解上给出了接口的 url
,所以 OpenFeign
绝对不会走负载均衡逻辑,而是从 LoadBalancerFeignClient
对象中拿到委托对象 feign.okhttp.OkHttpClient
创建接口的代理对象,所以最终调用接口发起请求时使用的也是同一个 feign.okhttp.OkHttpClient
。
OpenFeign
为每个 Client
提供一个环境隔离的 AnnotationConfigApplicationContext
,以实现为不同 Client
注册不同的配置 Bean
,如重试器 Retryer
、请求拦截器 RequestInterceptor
等。
每个 Client
不是说每个使用 @FeignClient
注解注释的接口,而是多个 name
相同的被 @FeignClient
注解注释的接口集合,这组接口都指向同一个服务提供者。
调用内部服务我们可能不会使用 OpenFeign
的重试机制,而是使用 Ribbon
的重试机制。只有在使用 OpenFeign
调用第三方接口时才有必要使用 OpenFeign
的重试机制。
复杂的实现可通过获取 FeignContext
去为每个 Client
注入配置类。有趣的是, FeignContext
是一个 NamedContextFactory
,为每个 Client
单独提供一个 AnnotationConfigApplicationContext
,而 Ribbon
的 SpringClientFactory
也是一个 NamedContextFactory
,也是为每个 Client
单独提供一个 AnnotationConfigApplicationContext
。
当我们使用 @FeignClient
注解注释一个接口时,如果指定了 Url
,且 Url
是以 http
开头的,则不会走 Ribbon
负载均衡,根据这一定律,我们就能很明确的知道,什么情况下使用 Ribbon
的重试机制,而什么情况下可以使用 OpenFeign
的重试机制。
由于每个 Client
是环境隔离的,除了可通过获取 FeignContext
去为每个 Client
注入配置类之外, @FeignClient
注解的 configuration
属性也可用来导入配置类。
创建配置类。
public class DefaultFeignRetryConfig { @Bean public Retryer retryer() { return new MyRetry(); } private static class MyRetry implements Retryer { /** * 最大重试次数 */ private final static int retryerMax = 1; /** * 当前重试次数 */ private int currentRetryCnt = 0; @Override public void continueOrPropagate(RetryableException e) { if (currentRetryCnt > retryerMax) { throw e; } // 连接异常时重试 if (e.getCause() instanceof ConnectException) { currentRetryCnt++; return; } throw e; } @Override public Retryer clone() { return new MyRetry(); } } } 复制代码
给 @FeignClient
注解的 configuration
属性添加该配置类。
@FeignClient(name = "alipay", path = "/v1", url = "${fegin-client.alipay-url}", configuration = {DefaultFeignRetryConfig.class}) 复制代码
OpenFeign
提供请求拦截器以便我们可以实现一些额外操作,例如拦截请求,在请求头添加 Basic
授权信息。
与配置 OpenFeign
的重试器一样,配置拦截器也可在 Client
的配置类中注入多个请求拦截器( RequestInterceptor
),多个请求拦截器名称不能相同。
例如,调用某支付公司的支付接口需要 Basic
授权,那么我们需要注册一个 BasicAuthRequestInterceptor
为所有请求添加授权头。
public class DefaultFeignRetryConfig { // 添加授权拦截器 @Bean("basicAuthRequestInterceptor") public BasicAuthRequestInterceptor basicAuthRequestInterceptor() { return new BasicAuthRequestInterceptor("test", "test123456"); } } 复制代码
最后确保已经给 @FeignClient
注解的 configuration
属性添加该配置类。
@FeignClient(name = "alipay", path = "/v1", url = "${fegin-client.alipay-url}", configuration = {DefaultFeignRetryConfig.class}) 复制代码