专栏首页Java艺术玩转OpenFeign

玩转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})

本文分享自微信公众号 - Java艺术(javaskill),作者:wujiuye

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-07-07

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 解决AOP拦截Mapper方法不知道事务是否回滚的尴尬问题

    最近做的一些需求遇到一个棘手的问题,例如埋点事件、AOP切面更新缓存等,可以感知事务的存在,却无法得知事务最终是回滚了还是提交了。

    Java艺术
  • Dubbo+Nacos项目二次改造之分布式事务解决方案

    本来想部署分布式全链路调用链追踪监视工具的,比如Skywalking。但考虑到至少需要部署ES或者HBase来存储大量调用日记,算了算每个平台部署一套监视工具也...

    Java艺术
  • 解析JSON数组正常,却在获取数组元素时抛出了类型转换异常

    下面这段代码会抛出类型转换异常(ClassCastException),JVM给出的解释是:不能将Double类型对象转换String类型 (java.lang...

    Java艺术
  • 网格映射

    对于两个网格S和T,它们之间的映射F:S -> T,可以根据根据S和T的相似度来进行分类:

    NT4.4
  • JPA 注解学习

    最近学习hibernate注解形式配置POJO类,将注解的解析记下来,以备以后使用。

    一个会写诗的程序员
  • C++之继承相关问题——菱形继承&继承

    即两个派生类继承同一个基类,同时两个派生类又作为基本继承给同一个派生类。这种继承形如菱形,故又称为菱形继承。

    海盗船长
  • ASP.Net请求处理机制初步探索之旅 - Part 2 核心

    开篇:上一篇我们了解了一个请求从客户端发出到服务端接收并转到ASP.Net处理入口的过程,这篇我们开始探索ASP.Net的核心处理部分,借助强大的反编译工具,我...

    Edison Zhou
  • c++ 虚继承

    拾点阳光
  • fasfdfs安装记录(CentOS7)

    1. 安装libfastscommon时,git报错:Peer reports incompatible or unsupported pro

    休辞醉倒
  • 聊聊java中的多继承,解决Java8接口default方法多继承冲突问题【享学Java】

    众所周知,Java是一种面向对象的只允许单继承的语言,这是每个Java程序员从业者都知道定理。 本文的目的,主要从两个方面来思考Java单继承的这个问题:

    YourBatman

扫码关注云+社区

领取腾讯云代金券