转载

玩转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
正文到此结束
Loading...