文章目录

  • 前言
  • 一、使用
    • 1. Feign 使用:
    • 2. SpringCloudOpenFeign
    • 3. 区别?
  • 二、原理
    • 1. Feign
    • 2. SpringCloudOpenFeign
  • 总结

前言

参考相关版本:

  • feign-core-10.10.1
  • spring-cloud-starter-openfeign:2.2.5.RELEASE

思考一下,你目前正在使用微服务体系,一个普通的用户请求可能会在微服务之间多次调用,而途径的每个微服务都需要原始请求的部分参数,你会如何传递这些参数?

在之前的文章中,我们了解到,Feign 的本质就是 JAVA 易用版的 HTTP 上层封装,本质还是 HTTP 调用,点击了解详情

想要原始请求参数在微服务之间流转,本质就是在调用下游服务的 HTTP 请求头上添加这些参数,最好还是业务逻辑无侵入性。

Feign 提供了拦截器机制,在真正 HTTP 调用之前,执行拦截器逻辑,你只需要实现特定的拦截器即可,业务逻辑层无感知。


一、使用

在使用上,我们主要从原生 Feign 和 Spring 体系下整合 Feign 来看具体的使用方式。

你需要注意的是,SpringCloud-OpenFeign 底层也是依赖于 Feign,只不过在使用上提供了一些便利而已。

1. Feign 使用:

原生 feign 的拦截器使用方式:

static class ForwardedForInterceptor implements RequestInterceptor {@Override public void apply(RequestTemplate template) {template.header("X-Forwarded-For", "origin.host.com");}}public class Example {public static void main(String[] args) {Bank bank = Feign.builder() .decoder(accountDecoder) .requestInterceptor(new ForwardedForInterceptor()) .target(Bank.class, "https://api.examplebank.com");}}

简单的说,实现 RequestInterceptor 拦截器,并手动添加至 feignClient 实例中,在真实请求调用时发挥作用。

2. SpringCloudOpenFeign

如果你习惯使用 Spring 框架,那么 SpringCloud-OpenFeign 应该是首选,使用起来也是相当方便,主要有以下几种使用方式:

1)自定义 Bean:

@Configurationpublic class FooConfiguration {@Beanpublic Contract feignContract() {return new feign.Contract.Default();}@Beanpublic BasicAuthRequestInterceptor basicAuthRequestInterceptor() {return new BasicAuthRequestInterceptor("user", "password");}}

这种是全局有效,会自动添加到所有 FeignClient 的拦截器列表中。

2)配置文件中指定拦截器:

我们除了创建拦截器 bean 实列外,还可以直接在 application.yml 配置文件中添加配置:

feign:client:config:feignName:connectTimeout: 5000readTimeout: 5000loggerLevel: fullerrorDecoder: com.example.SimpleErrorDecoderretryer: com.example.SimpleRetryerrequestInterceptors:- com.example.FooRequestInterceptor- com.example.BarRequestInterceptorencoder: com.example.SimpleEncoderdecoder: com.example.SimpleDecoder...

这种方式,本质也是在扫描并初始化 FeignClient 的时候,创建拦截器实例,并添加到 FeignClient 实例中。

你也看到了,这种是应用于特定的 feignName,即特定的 FeignClient 实例。

3)FeignClient 的配置选择中指定拦截器:

@FeignClient(contextId = "fooClient", name = "stores", configuration = FooConfiguration.class)public interface FooClient {//..}

拦截器应用范围,当前 FeignClient 实例。

3. 区别?

Feign 拦截器的使用非常简单:

  • 首先,实现 RequestInterceptor
  • 将实现类添加至 FeignClient 的实例中

值得注意的是,SpringCloud-OpenFeign 与原生 Feign 使用方式的主要区别在于,Spring 提供了自动扫描并创建 FeignClient 实例的能力,因此,其拦截器也是自动注入的

当然,本质还是原生 Feign 的使用方式而已。

二、原理

原理上我们主要了解,feign 拦截器何时发挥作用,以及 Spring 又是如何整合 Feign,我们从第一视角,了解完整的一条链路。

1. Feign

feign-core 核心包提供了动态代理类 SynchronousMethodHandler,该类是 feign 调用的核心处器,包括 http 调用、拦截器处理等等。

Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {// 拦截器处理入口Request request = targetRequest(template);...Response response;long start = System.nanoTime();try {response = client.execute(request, options);// ensure the request is set. TODO: remove in Feign 12response = response.toBuilder().request(request).requestTemplate(template).build();} catch (IOException e) {if (logLevel != Logger.Level.NONE) {logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start));}throw errorExecuting(request, e);} ...}

是不是很熟悉?典型的 http 调用,在调用之前放置了拦截器处理逻辑。

Request targetRequest(RequestTemplate template) {for (RequestInterceptor interceptor : requestInterceptors) {interceptor.apply(template);}return target.apply(template);}

就这样,我们在自定义拦截器的处理逻辑就被应用到了 http 请求过程中,下游直接从 http 请求中取就完事了。

2. SpringCloudOpenFeign

介绍完了拦截器如何应用于 http 请求中,接下来我们看看 Spring 体系下,拦截器如何被加载?

在原生 feign 使用过程中,拦截器是这样添加的:

Bank bank = Feign.builder() .decoder(accountDecoder) .requestInterceptor(new ForwardedForInterceptor()) .target(Bank.class, "https://api.examplebank.com");

当然,Spring 有自己强大的 IOC 容器管理,为我们提供了更加方便且优雅的添加方式。

SpringCloud-OpenFeign 提供了注解 @FeignClient定义 feign 请求客户端,只要我们通过注解 @EnableFeignClients 开启 feign 客户端注解扫描,这些 client 就会被 Spring IOC 解析成 bean 并被管理起来。

我们知道 Spring 在创建 bean 的过程中,可以通过 配置、yaml 属性等解析 bean 的参数并注入,我们的拦截器也是这个时候被添加,对应了我们使用篇的几种方式。

关于 FeignClient bean 的创建,我们可以主要关注 FeignClientFactoryBean 类,见名思义,xxxFactoryBean 你懂得。

protected void configureFeign(FeignContext context, Feign.Builder builder) {FeignClientProperties properties = applicationContext.getBean(FeignClientProperties.class);FeignClientConfigurer feignClientConfigurer = getOptional(context,FeignClientConfigurer.class);setInheritParentContext(feignClientConfigurer.inheritParentConfiguration());if (properties != null && inheritParentContext) {if (properties.isDefaultToProperties()) {// 从配置解析参数并注入 client beanconfigureUsingConfiguration(context, builder);configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()),builder);configureUsingProperties(properties.getConfig().get(contextId), builder);}else {configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()),builder);configureUsingProperties(properties.getConfig().get(contextId), builder);configureUsingConfiguration(context, builder);}}else {configureUsingConfiguration(context, builder);}}

值得注意的, feign 的拦截器可以定义多个。如果你定义了多个拦截器 bean 的话,都会被注入。

不过,如果你拦截器的 beanName 出现同名的话,会出现覆盖的情况。

总结

本文主要讲解了 feign 拦截器的几种主要应用方式、拦截器的工作入口等。

另外,在 SpringCloud 体系下,我们还介绍了拦截器如何被自动扫描并装配到 FeignClient 的 bean 实例中。

feign 拦截器的应用应该是非常广泛的,如果你使用的 SpringCloud 体系,应该更有感触。我们一般可以用来权限认证、请求头参数传递 …

在笔者的生产项目中,一般是将其放在公共包里,每个微服务项目直接依赖公共包,便可实现参数的上下游传递,十分方便!!!

相关参考:

  • OpenFeign # Github
  • spring-cloud-feign # docs
  • Feign-Hystrix 熔断降级,一文看透本质