草庐IT

Feign 应用之 RequestInterceptor 拦截器,超实用指南

柏油 2023-05-16 原文

文章目录


前言

参考相关版本:

  • 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:

@Configuration
public class FooConfiguration {
    @Bean
    public Contract feignContract() {
        return new feign.Contract.Default();
    }

    @Bean
    public BasicAuthRequestInterceptor basicAuthRequestInterceptor() {
        return new BasicAuthRequestInterceptor("user", "password");
    }
}

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


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

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

feign:
  client:
    config:
      feignName:
        connectTimeout: 5000
        readTimeout: 5000
        loggerLevel: full
        errorDecoder: com.example.SimpleErrorDecoder
        retryer: com.example.SimpleRetryer
        requestInterceptors:
          - com.example.FooRequestInterceptor
          - com.example.BarRequestInterceptor
        encoder: com.example.SimpleEncoder
        decoder: 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 12
      response = 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 bean
				configureUsingConfiguration(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 体系,应该更有感触。我们一般可以用来权限认证、请求头参数传递 …

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




相关参考:

有关Feign 应用之 RequestInterceptor 拦截器,超实用指南的更多相关文章

  1. ruby - 将差异补丁应用于字符串/文件 - 2

    对于具有离线功能的智能手机应用程序,我正在为Xml文件创建单向文本同步。我希望我的服务器将增量/差异(例如GNU差异补丁)发送到目标设备。这是计划:Time=0Server:hasversion_1ofXmlfile(~800kiB)Client:hasversion_1ofXmlfile(~800kiB)Time=1Server:hasversion_1andversion_2ofXmlfile(each~800kiB)computesdeltaoftheseversions(=patch)(~10kiB)sendspatchtoClient(~10kiBtransferred)Cl

  2. ruby - 在 Ruby 中编写命令行实用程序 - 2

    我想用ruby​​编写一个小的命令行实用程序并将其作为gem分发。我知道安装后,Guard、Sass和Thor等某些gem可以从命令行自行运行。为了让gem像二进制文件一样可用,我需要在我的gemspec中指定什么。 最佳答案 Gem::Specification.newdo|s|...s.executable='name_of_executable'...endhttp://docs.rubygems.org/read/chapter/20 关于ruby-在Ruby中编写命令行实用程序

  3. ruby-on-rails - Rails 应用程序之间的通信 - 2

    我构建了两个需要相互通信和发送文件的Rails应用程序。例如,一个Rails应用程序会发送请求以查看其他应用程序数据库中的表。然后另一个应用程序将呈现该表的json并将其发回。我还希望一个应用程序将存储在其公共(public)目录中的文本文件发送到另一个应用程序的公共(public)目录。我从来没有做过这样的事情,所以我什至不知道从哪里开始。任何帮助,将不胜感激。谢谢! 最佳答案 无论Rails是什么,几乎所有Web应用程序都有您的要求,大多数现代Web应用程序都需要相互通信。但是有一个小小的理解需要你坚持下去,网站不应直接访问彼此

  4. ruby - 无法运行 Rails 2.x 应用程序 - 2

    我尝试运行2.x应用程序。我使用rvm并为此应用程序设置其他版本的ruby​​:$rvmuseree-1.8.7-head我尝试运行服务器,然后出现很多错误:$script/serverNOTE:Gem.source_indexisdeprecated,useSpecification.Itwillberemovedonorafter2011-11-01.Gem.source_indexcalledfrom/Users/serg/rails_projects_terminal/work_proj/spohelp/config/../vendor/rails/railties/lib/r

  5. ruby-on-rails - Rails 应用程序中的 Rails : How are you using application_controller. rb 是新手吗? - 2

    刚入门rails,开始慢慢理解。有人可以解释或给我一些关于在application_controller中编码的好处或时间和原因的想法吗?有哪些用例。您如何为Rails应用程序使用应用程序Controller?我不想在那里放太多代码,因为据我了解,每个请求都会调用此Controller。这是真的? 最佳答案 ApplicationController实际上是您应用程序中的每个其他Controller都将从中继承的类(尽管这不是强制性的)。我同意不要用太多代码弄乱它并保持干净整洁的态度,尽管在某些情况下ApplicationContr

  6. ruby-on-rails - 如何在我的 Rails 应用程序 View 中打印 ruby​​ 变量的内容? - 2

    我是一个Rails初学者,但我想从我的RailsView(html.haml文件)中查看Ruby变量的内容。我试图在ruby​​中打印出变量(认为它会在终端中出现),但没有得到任何结果。有什么建议吗?我知道Rails调试器,但更喜欢使用inspect来打印我的变量。 最佳答案 您可以在View中使用puts方法将信息输出到服务器控制台。您应该能够在View中的任何位置使用Haml执行以下操作:-puts@my_variable.inspect 关于ruby-on-rails-如何在我的R

  7. ruby-on-rails - 如何在 Gem 中获取 Rails 应用程序的根目录 - 2

    是否可以在应用程序中包含的gem代码中知道应用程序的Rails文件系统根目录?这是gem来源的示例:moduleMyGemdefself.included(base)putsRails.root#returnnilendendActionController::Base.send:include,MyGem谢谢,抱歉我的英语不好 最佳答案 我发现解决类似问题的解决方案是使用railtie初始化程序包含我的模块。所以,在你的/lib/mygem/railtie.rbmoduleMyGemclassRailtie使用此代码,您的模块将在

  8. 世界前沿3D开发引擎HOOPS全面讲解——集3D数据读取、3D图形渲染、3D数据发布于一体的全新3D应用开发工具 - 2

    无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD

  9. 叮咚买菜基于 Apache Doris 统一 OLAP 引擎的应用实践 - 2

    导读:随着叮咚买菜业务的发展,不同的业务场景对数据分析提出了不同的需求,他们希望引入一款实时OLAP数据库,构建一个灵活的多维实时查询和分析的平台,统一数据的接入和查询方案,解决各业务线对数据高效实时查询和精细化运营的需求。经过调研选型,最终引入ApacheDoris作为最终的OLAP分析引擎,Doris作为核心的OLAP引擎支持复杂地分析操作、提供多维的数据视图,在叮咚买菜数十个业务场景中广泛应用。作者|叮咚买菜资深数据工程师韩青叮咚买菜创立于2017年5月,是一家专注美好食物的创业公司。叮咚买菜专注吃的事业,为满足更多人“想吃什么”而努力,通过美好食材的供应、美好滋味的开发以及美食品牌的孵

  10. 【鸿蒙应用开发系列】- 获取系统设备信息以及版本API兼容调用方式 - 2

    在应用开发中,有时候我们需要获取系统的设备信息,用于数据上报和行为分析。那在鸿蒙系统中,我们应该怎么去获取设备的系统信息呢,比如说获取手机的系统版本号、手机的制造商、手机型号等数据。1、获取方式这里分为两种情况,一种是设备信息的获取,一种是系统信息的获取。1.1、获取设备信息获取设备信息,鸿蒙的SDK包为我们提供了DeviceInfo类,通过该类的一些静态方法,可以获取设备信息,DeviceInfo类的包路径为:ohos.system.DeviceInfo.具体的方法如下:ModifierandTypeMethodDescriptionstatic StringgetAbiList​()Obt

随机推荐