基于Feign的局部请求拦截

2023-10-30

由于项目的要求,不能对所有基于Feign的进行拦截,需要对不同的Feign请求进行不同的拦截,经过资料的收集整理以及SpringCloud中对于Feign的集成的源码的阅读,解决了针对Feign请求的局部拦截

本项目中SpringCloud的版本是Camden.SR6版本

背景说明

在既有的项目上进行二次开发,服务A需要请求服务B同时需要将服务A中请求的消息头相关信息传送给服务B,但是由于既有项目中的相关设计,不支持feign请求的全局拦截,只能针对服务A请求服务B的feign请求进行拦截,所以开发了如下的方法;

这里说明下,之所以采用Feign是由于Feign添加支持负载均衡,这点尤为重要。

思路说明

既然当前SpringCloud的版本不支持Feign请求的拦截,那么只能自己开发拦截的方法来拦截Feign请求了,整理资料有如下两种思路:

  1. Feign内部也是使用Ribbon来完成支持负载均衡的,所以抛开Feign,直接使用Ribbon也是可以的;

为了扩展方便,可以采用扫描自定义注解和AOP拦截的方式,然后通过前置方法将消息头相关内容存储到请求中

这个思路简单易用,而且方法都是自己开发的,出现问题,定位和修改都是很容易的,但是这种方法也相当于重新开发了一种新的功能,工作量和代码量肯定是不小的,

  1. 还是使用Feign,既然SpringCloud当前版本不支持,那么就利用原生的Feign来自己封装;

SpringCloud的@FeignClient也是基于原生的Feign的基础上进行封装的,所以我们也可以开发新的封装,使之支持目前的需求,对Feign的请求进行局部拦截

如果想进行新的封装,我们可以借鉴SPringCloud对Feign的封装方法,这里我们可以参考 FeignClient源码深度解析这篇文章,说的很详细,在这里感谢大佬的分享。

代码实现

废话不多说,为了让代码改动量小,并且利用Feign的特性:(一个接口就可以访问其他的项目),我们选择第二种方法来实现

在这里对于SpringCloud支持Feign的封装思路就显得比较重要了,不过在这之前,我们可以使用原生的Feign来支持请求的拦截

首先是依赖的支持

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-feign</artifactId>
        <version>1.2.6.RELEASE</version>
    </dependency>
    
    <dependency>
        <groupId>com.netflix.feign</groupId>
        <artifactId>feign-ribbon</artifactId>
        <version>8.18.0</version>
    </dependency>
  1. 首先我们定义一个接口,该接口配置Feign访问其他项目的路径
    /**
     * @author: amos
     * @Description: 访问其他业务的请求
     * @date: 2019/12/23 0023 下午 17:39
     * @Version: V1.0
     */
    public interface BizClient {
        @RequestMapping(value = "/biz/list", method = RequestMethod.POST)
        Result list(@RequestBody BizDTO dto);
    }

注意:该接口上没有添加 @FeignClient 注解,因为目前项目是支持SpringCloud的Feign使用方式的,如果添加了注解,就会直接走SpringCloud的Feign请求方式
2. 原生的Feign使用方式

    /**
     * 
     * @author: amos
     * @Description: 基于原生的Feign请求来获取请求访问对象     
     * @date: 2020/2/19 0019 下午 16:09
     * @Version: V1.0
     */
    @Configuration
    public class BasicFeignBuilderConfig {
    
        public Client client;
    
        private HttpMessageConverter jsonConverter;
    
        private ObjectFactory<HttpMessageConverters> converter;
        private static final String CLINET_URL = "http://APPLICATION-NAME";
        /**
         * 初始化client
         */
        @PostConstruct
        public void initClient() {
            this.client = RibbonClient.create();
            this.jsonConverter = new MappingJackson2HttpMessageConverter(new ObjectMapper());
            this.converter = () -> new HttpMessageConverters(jsonConverter);
        }
         /**
         * 利用Feign来获取接口访问对象
         *
         * @param clazz
         * @param <T>
         * @return
         */
        public <T> T feignBuilderRequestInterceptor(Class<T> clazz) {
            T t = Feign.builder()
                    .encoder(new SpringEncoder(converter))
                    .client(client)
                    .decoder(new SpringDecoder(converter))
                    .contract(new SpringMvcContract())
                    .requestInterceptor(new FeignBasicTenantIdRequestInterceptor())
                    .target(clazz, CLINET_URL);
            return t;
        }   
        /**
         * 将BizClient注册到SpringContext的上下文中
         *
         * @return
         */
        @Bean("bizClient")
        public BizClient bizClient() {
            return this.feignBuilderRequestInterceptor(BizClient.class);
        }
    }

至此使用原生的Feign结束,但是一测试就报错

java.lang.RuntimeException: com.netflix.client.ClientException: Load balancer does not have available server for client: APPLICATION-NAME

从网上查询资料,并进行了相关的依赖和配置项 都没有生效

  1. 阅读源码,了解SpringCloud支持Feign的原理

上面的办法既然不可行,主要的问题是Ribbon识别不了我们的实例名,也就是代码中Client有问题,但是SpringCloud的Feign却是可以支持的,所以这里的关键就是SpringCloud中的Feign是怎么支持的Ribbon的,然后将他支持的方式移到我们目前代码,解决由于Ribbon造成的负载均衡的问题就可以了。

为此,我们需要阅读SpringCloud支持Feign方面的相关的源码,源码的阅读可以参考上面的博客链接,说明的非常详细,下面我们主要分析下源码中的代理工厂的代码;

FeignClientFactoryBean这个类就是FeignClient的代理工厂类,我们看下工厂类的入口getObject()方法:

    @Override
	public Object getObject() throws Exception {
	    // 从Spring的ApplicationContext中获取FeignContext
		FeignContext context = applicationContext.getBean(FeignContext.class);
		// 利用构造器来构造Feign的对象
		Feign.Builder builder = feign(context);
		.....
	}

这里我们主要看下构造器中是怎么获取Client对象的,我们需要知道他是怎么处理支持负载均衡的,我们追踪到对应的代码:

    protected <T> T getOptional(FeignContext context, Class<T> type) {
		return context.getInstance(this.name, type);
	}
	
    protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
			HardCodedTarget<T> target) {
		// 这里就是我们需要的client对象 
		// 通过上面的代码我们知道 FeignContext 中获取对应的Bean
		Client client = getOptional(context, Client.class);
		if (client != null) {
			builder.client(client);
			Targeter targeter = get(context, Targeter.class);
			return targeter.target(this, builder, context, target);
		}

		throw new IllegalStateException(
				"No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-ribbon?");
	}

至此,我们知道了Client对象主要来源于FeignContext中,而FeignContext是来源于ApplicationContext中,到这里就非常请求了,我们需要从ApplicationContext中获取FeignContext,然后再从FeignContext中获取Client对象。

所以我们需要改造上面 BasicFeignBuilderConfig的代码:

    /**
     * 
     * @author: amos
     * @Description: 基于原生的Feign请求来获取请求访问对象     
     * @date: 2020/2/19 0019 下午 16:09
     * @Version: V1.0
     */
    @Configuration
    public class BasicFeignBuilderConfig implements ApplicationContextAware{
    
        public Client client;
    
        private HttpMessageConverter jsonConverter;
    
        private ObjectFactory<HttpMessageConverters> converter;
        
        private static final String CLINET_URL = "http://APPLICATION-NAME";
        
        private ApplicationContext applicationContext;

        @Override
        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
            this.applicationContext = applicationContext;
        }
        /**
         * 初始化client
         */
        @PostConstruct
        public void initClient() {
            this.client =  FeignContext context = applicationContext.getBean(FeignContext.class);
            this.jsonConverter = new MappingJackson2HttpMessageConverter(new ObjectMapper());
            this.converter = () -> new HttpMessageConverters(jsonConverter);
        }
         /**
         * 利用Feign来获取接口访问对象
         *
         * @param clazz
         * @param <T>
         * @return
         */
        public <T> T feignBuilderRequestInterceptor(Class<T> clazz) {
            T t = Feign.builder()
                    .encoder(new SpringEncoder(converter))
                    .client(client)
                    .decoder(new SpringDecoder(converter))
                    .contract(new SpringMvcContract())
                    .requestInterceptor(new FeignBasicTenantIdRequestInterceptor())
                    .target(clazz, CLINET_URL);
            return t;
        }   
        /**
         * 将BizClient注册到SpringContext的上下文中
         *
         * @return
         */
        @Bean("bizClient")
        public BizClient bizClient() {
            return this.feignBuilderRequestInterceptor(BizClient.class);
        }
    }

上面的代码主要是 类实现ApplicationContextAware接口来获取 ApplicationContext对象,然后从ApplicationContext对象中获取FeignContext对象,再获取到我们需要的Client对象即可;

至此已经完全结束了,我们可以在业务代码中直接注入 BizClient 直接调用对应得方法了。

    @Autowired
    BizClient bizClient
    
    public Result list(BizDTO dto){
        return bizClient.list(dto);
    }

上面的代码还可以再进行封装,如果有多个BizClient的业务请求,可以通过自定义注解来实现系统在启动的时候,扫描自定义的注解,然后同样利用代理工厂的方法生成实例对象,然后注入到Spring的ApplicationContext中,方便业务直接拿来使用。

逻辑和Spring支持Feign的逻辑是一样的,主要依赖ImportBeanDefinitionRegistrarResourceLoaderAwareBeanClassLoaderAware三个类。

我会在下一篇博文中基于该方法来说明,如何实现系统启动将自定义注解的bean注入到Spring的ApplicationContext

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

基于Feign的局部请求拦截 的相关文章

随机推荐

  • 游戏开发unity杂项知识系列:如何查看Unity API文档

    https blog csdn net scalince article details 75199853
  • 字典dict常用操作

    gt gt gt dict x 1 y 2 x 1 y 2 gt gt gt dict1 dict x 1 y 2 gt gt gt dict1 x 1 y 2 gt gt gt dict
  • 家居商城小程序:打造舒适家居生活的优选平台

    随着人们对家居生活品质的追求 家居商城小程序成为提供便捷购物和个性化服务的不可或缺的工具 通过家居商城小程序 用户可以浏览并购买各类家居商品 如家具 装饰品 家纺等 同时 家居商城小程序能提供热销商品推荐 客户评价和在线客服等功能 家居商城
  • 矩阵基本运算(C++)

    闲着没事 将以前利用C 实现的矩阵各种运算整理了一下 分享一下 矩阵运算包括 1 二维矩阵创建 6 两矩阵上下叠加 2 两矩阵相加 7 矩阵左右叠加 3 两矩阵相减 8 矩阵转置 4 两矩阵相乘 9 矩阵求逆 5 一个数字与矩阵相乘 10
  • Vue 项目下载文件最佳解决方案

    大厂技术 高级前端 Node进阶 点击上方 程序员成长指北 关注公众号 回复1 加入高级Node交流群 原文链接 https juejin cn post 7062888582465191944作者 远航 开发中经常遇到这样的功能 用户将文
  • 文字检测定位--SAST

    文字检测过程分两步 第一步为检测出文字所在的位置 第二步根据检测框里的内容进行文字识别 SAST是一种自然场景下的文本检测算法 论文地址 https arxiv org abs 1908 05498 一 背景知识 1 语义分割算法 FCN全
  • 如何使用vue的渲染函数 API:h函数创建一个el-select弹出框

    如何使用vue的h函数创建一个el select弹出框 效果示例图 示例代码 h 创建虚拟 DOM 节点 vnode 效果示例图 示例代码 const h this createElement this msgbox title 是否发送短
  • SAE J1939转ModBus RTU模块 YMMCS-7001使用说明及心得体会

    SAE J1939总线是工程机械行业常用总线 发动机 车辆等运行参数都可以通过该总线获取 常规电控系统以PLC加组态屏方案为主 具有RS485接口 ModBus RTU协议进行总线组网 YMMCS 7001 协议转换模块直接可将SEA J1
  • 【Linux】写时复制(CopyOnWrite)

    目录 什么是写时拷贝 写时拷贝原理 原文 https blog csdn net Code beeps article details 92838520 http c biancheng net view 1272 html 什么是写时拷贝
  • bootstrap-table 添加行保留原数据

    添加行 保留原数据 function addSgbzColumn var count bootstrap table bootstrapTable getData length var params new Array for var da
  • 使用GraphEdit调试你的Filter

    Posted by 令狐葱 on 2006年03月12日 今天看到在VC6 0里面可以直接使用 GraphEdit调试你自己编写的 Filter 但是反复试了多次就是摸不着门路 后来在网上找资料 又参考了一些书 问了郭郭和小侯 才终于把Gr
  • 线程类的构造方法与成员方法

    1 构造方法 public Thread 分配一个新的线程对象 public Thread String name 分配一个指定名字的新的线程对象 public Thread Runnable target 分配一个带有指定目标新的线程对象
  • mybatis详解(必会)

    文章目录 什么是mybatis 什么是持久化 什么是持久层 为什么要学习mybatis 配置解析 生命周期和作用域 ResultMap映射 一对多和多对一的处理 动态sql 缓存 EhCache 什么是mybatis MyBatis 是一款
  • 面试官问“你还有什么需要了解的吗”——应该这样回答

    最近被追问 如何化解面试官说出 你还有什么需要从我这里了解的吗 时你竟无言以对的尴尬 比较不建议在这个时候向面试官提问琐碎日常 比如 有没有加班费啊喂 上下班时间是怎样啊 打车吃饭是不是可以报销嘞 即使你在来面试前并没有对这家公司做过少许调
  • 机器学习(十四)SVM分类案例

    01 案例一 鸢尾花数据SVM分类 import numpy as np import pandas as pd from sklearn import svm from sklearn model selection import tra
  • Golang sync.pool对象池

    概览 Goalng中通过sync pool提供了对象池的实现来达到对象复用的目的 在netty中 也通过Recycle类实现了类似的对象池实现 在netty的对象池Recycle中 当A线程需要将B线程申请的对象回收到对象池中的时候 会专门
  • handleMessage的使用

    xml代码
  • YOLOv5学习笔记

    转载于 深入浅出Yolo系列之Yolov5核心基础知识完整讲解 江南研习社 CSDN博客 yolov5 1 网络结构 Yolov5官方代码中 给出的目标检测网络中一共有4个版本 分别是Yolov5s Yolov5m Yolov5l Yolo
  • 贪心算法:最优分解问题

    问题描述 设n是一个正整数 现要求将n分解为若干个互不相同的自然数的和 使这些自然数的乘积最大 代码如下 int BestMul int n int i j mul 1 int num 初始化一个数组 用来存放分解后的每个数 int a M
  • 基于Feign的局部请求拦截

    由于项目的要求 不能对所有基于Feign的进行拦截 需要对不同的Feign请求进行不同的拦截 经过资料的收集整理以及SpringCloud中对于Feign的集成的源码的阅读 解决了针对Feign请求的局部拦截 本项目中SpringCloud