转载

玩转OpenFeign

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

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

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

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

本篇内容:

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

配置OpenFeign使用OkHttp

OpenFeign 通过 Client 发送 http 请求,而默认的 Client 则是使用 HttpURLConnection 实现发送 http 请求的。

如果你觉得 HttpURLConnection 性能不行,你也可以通过自定义 Client 将发送 http 请求的动作切换到其它你认为更优秀的框架来完成。 OpenFeign 也为我们提供了两种选择,一种是使用 okhttp 框架,另一种是使用 apachehttpclient 框架。

OpenFeignRibbon 双剑合璧时,实际向服务提供者发起请求还是由 OpenFeignClient 完成,所以我们切换 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.enabledtrue
## 配置feign使用okhttp
feign:
  okhttp:
    enabled: true
复制代码
  • 3、未导入 Ribbon 包,即不使用 Ribbon ,项目中不存在 com.netflix.loadbalancer.ILoadBalancer 这个类;

而当项目中使用 Ribbon 时, OpenFeign 创建的不再是默认的 Default ,也不是 OkHttpClient ,而是 LoadBalancerFeignClientFeignRibbonClientAutoConfiguration 配置类被设置在 FeignAutoConfiguration 配置类之前完成自动配置, FeignRibbonClientAutoConfiguration 往容器中注入了 LoadBalancerFeignClient

@AutoConfigureBefore(FeignAutoConfiguration.class)
@Import({ HttpClientFeignLoadBalancedConfiguration.class,
		OkHttpFeignLoadBalancedConfiguration.class,
		DefaultFeignLoadBalancedConfiguration.class })
public class FeignRibbonClientAutoConfiguration {
}
复制代码

FeignRibbonClientAutoConfiguration 使用 @Import 导入三个自动配置 LoadBalancerFeignClient 的配置类,但最终只会有一个被导入,当我们配置 feign.okhttp.enabledtrue ,且项目中添加了 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);
	}
}
复制代码

虽然 OpenFeignRibbon 整合使用时, OpenFeign 使用的 ClientLoadBalancerFeignClient ,但这个 LoadBalancerFeignClient 只是实现负载均衡的桥梁,实际还是通过委托模式将发送请求的工作委托给其它 Client 完成,而这里使用的就是 feign.okhttp.OkHttpClient

通过前面的源码分析的学习,我们知道,当配置 @FeignClient 注解的 url 属性时,不会使用 LoadBalancerFeignClient ,但我们配置的 feign.okhttp.OkHttpClient 依然生效。原因是 OpenFeign 不会再创建新的 Client ,但会从 LoadBalancerFeignClient 对象中取得委托对象 feign.okhttp.OkHttpClient

我们也可以在 FeignClientFactoryBeangetTarget 方法添加断点调试,以验证使用 @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 ,而 RibbonSpringClientFactory 也是一个 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...