玩转OpenFeign

经过前面几篇的源码分析,我们对 OpenFeign
Ribbon
也相对熟悉了。

看框架源码的目的就是解决我们的一些疑惑,能够知其然并知其所以然,以及用好框架。

很多时候,我们需要在项目中调用一些第三方接口,例如对接支付宝支付、微信支付,调用支付接口。如果项目中引入了 OpenFeign
,那么我们是否可以使用 OpenFeign
去调用第三方接口呢?答案肯定是可以的。

虽然调用第三方接口不需要服务发现,所以也不需要使用 Ribbon
实现负载均衡,但我们依然可以单独使用 OpenFeign
。使用 OpenFeign
不仅能够简化调用接口的步骤,也能顺便使用 OpenFeign
提供的重试机制,不需要再编写一个 HttpUtils
工具类,何乐而不为呢。

本篇内容:

  • 配置 OpenFeign
    使用 OkHttp
  • OpenFeign
    的重试配置
  • OpenFeign
    的拦截器配置

配置OpenFeign使用OkHttp

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

该配置类生效的前提条件很多:

  • 1、项目中导入了 feign-okhttp
    包,即当前项目的 classpath
    下存在一个 feign.okhttp.OkHttpClient
    类,该类实现了 Client
    接口;
<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-okhttp</artifactId>
    <version>11.0</version>
</dependency>
复制代码
  • 2、在 application.yaml
    中配置 feign.okhttp.enabled
    true
## 配置feign使用okhttp
feign:
  okhttp:
    enabled: true
复制代码
  • 3、未导入 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的重试配置

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的拦截器配置

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

原文 

https://juejin.im/post/5f032babe51d4534c14daa04

本站部分文章源于互联网,本着传播知识、有益学习和研究的目的进行的转载,为网友免费提供。如有著作权人或出版方提出异议,本站将立即删除。如果您对文章转载有任何疑问请告之我们,以便我们及时纠正。

PS:推荐一个微信公众号: askHarries 或者qq群:474807195,里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

转载请注明原文出处:Harries Blog™ » 玩转OpenFeign

赞 (0)
分享到:更多 ()

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址