工具篇--SpringCloud--openFeign--Feign.builder()自定义客户端

文章目录

  • 前言
  • 一、自定义客户端:
    • 1.1 定义外部接口类:
    • 1.2 接口代理类生成:
    • 1.3 方法的远程调用:
  • 二、Feign.builder()自定义客户端原理:
    • 2.1 FeignClientFactoryBean
    • 2.2 客户端的配置设置:
    • 2.3 代理类的生成:
  • 总结


前言

提示:本文springboot 版本 :2.7.16,spring-cloud-starter-openfeign 版本:3.1.5

在项目中有时候我们既需要和本身集群内的微服务进行调用,也会和外部第三方的服务进行服务调用;在我们自身微服务调用时我们通常已经定义了 RequestInterceptor 并通过@Component 交由Spring 进行管理(在此拦截器中可能对header 进行了操作),此时当我们在这个服务中又调用了第三方服务(在此拦截器中也需要对header 进行了特殊操作),此时就有可能造成多个 RequestInterceptor 冲突,此时我们可以考虑通过-Feign.builder()自定义客户端;


一、自定义客户端:

1.1 定义外部接口类:

public interface SyncFeign3Service {
    @GetMapping("/get")
    Map search(@RequestParam("wd") String wd);

    @PostMapping("/get")
    Map getData(@RequestBody Map<String, Object> map, @RequestHeader("token") String token);
}

这个接口和我们平常写的业务接口类没有任何区别;

1.2 接口代理类生成:


import com.example.springmvctest.feign.api.SyncFeign3Service;
import feign.Contract;
import feign.Feign;
import feign.Logger;
import feign.codec.Decoder;
import feign.codec.Encoder;
import feign.slf4j.Slf4jLogger;
import lombok.Getter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.openfeign.FeignClientsConfiguration;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

@Getter
@Configuration
@Import(FeignClientsConfiguration.class)
public class CustomerFeign {


    private SyncFeign3Service fooClient;


    @Autowired
    public CustomerFeign(Encoder encoder, Decoder decoder, Contract contract) {
//        List<RequestInterceptor> requestInterceptors = new ArrayList<>();
//        requestInterceptors.add(new MyInterceptor3());
        Logger slf4jLogger = new Slf4jLogger(SyncFeign3Service.class);
        // 设置5秒的连接超时和10秒的读取超时,true 表示 如果接收到请求是重定向则直接访问 这个重定向的地址
        Request.Options options = new Request.Options(5000, TimeUnit.MILLISECONDS, 10000, TimeUnit.MILLISECONDS, true);
        this.fooClient = Feign.builder()
                .encoder(encoder)
                .decoder(decoder)
                .contract(contract)
                .options(options)
                .requestInterceptor(new MyInterceptor3())
//                .requestInterceptors(requestInterceptors)
                .logger(slf4jLogger)
                .logLevel(Logger.Level.FULL)
                .target(SyncFeign3Service.class, "http://localhost:8081/test");

    }
}

  • @Configuration 标注可以被spring 管理;
  • FeignClientsConfiguration 导入该类获取到已经在容器中存在的 encoder,decoder,contract 的bean
  • 最后通过Feign.builder() 构建客户端;
  • requestInterceptor 可以单个添加拦截,requestInterceptors 可以匹配添加拦截;
  • logger 用来定义日志的实现类,logLevel 定义feign日志级别;
  • target 通过target 方法生成代理对象 并赋值给 接口对象,SyncFeign3Service ;

1.3 方法的远程调用:

@RestController
@RequestMapping("/test/feign")
class FooController {

    @Autowired
    private CustomerFeign fooClient;

    @GetMapping("/getData3")
    public Map getData() {
        Map<String,Object> map = new HashMap<>();
        map.put("name","lis");
        map.put("age",18);
        return fooClient.getFooClient().getData(map,"1234556");
    }
}

在控制器中注入 接口代理类,进而完成方法的调用;如果想要看下其具体的实现可以继续向下阅读;

二、Feign.builder()自定义客户端原理:

省流总结 :Feign.builder() 自定义客户端 最后也是通过 Feign 类中的 Builder类 target 方法为其生成代理对象

在这里插入图片描述
我们通过Feign.builder()自定义的客户端 最终也是调用了Builder类 target 方法 ,而链式调用设置encoder,decoder,contract ,也是参考的 FeignClientFactoryBean 中 getTarget() 方法;

我们知道 openfeign 是个rpc 框架,本身实现是用了jdk 的动态代理 来为接口生成代理对象,最终业务的实现是交由代理对象 去创建http 请求,向远程服务发起调用;而在openfeign·中用来生成feign 接口的动态代理类就是FeignClientFactoryBean ,每个feign 接口都会生成一个FeignClientFactoryBean 的bean 对象,他们通过在feign 接口中定义的contextId 进行区分

2.1 FeignClientFactoryBean

因为 FeignClientFactoryBean 实现 了FactoryBean 接口,最终spring 在获取 FeignClientFactoryBean 的bean 时,会调用getObject() 方法获取最终的bean:

public Object getObject() {
    return this.getTarget();
}

调用了本身类中的getTarget 方法:

 <T> T getTarget() {
 		// 获取spring 容器中的 FeignContext bean
        FeignContext context = this.beanFactory != null ? (FeignContext)this.beanFactory.getBean(FeignContext.class) : (FeignContext)this.applicationContext.getBean(FeignContext.class);
        // 重要方法 创建feign 客户端 并对其编码,解码,连接超时等进行配置
    Feign.Builder builder = this.feign(context);
    // 获取feign 接口中的url,如果 url 不以http 开头,则说明要通过微服务的名字 去具体的nacos/Eureka 去拿到一个url
    if (!StringUtils.hasText(this.url)) {
        if (LOG.isInfoEnabled()) {
            LOG.info("For '" + this.name + "' URL not provided. Will try picking an instance via load-balancing.");
        }

        if (!this.name.startsWith("http")) {
            this.url = "http://" + this.name;
        } else {
            this.url = this.name;
        }

        this.url = this.url + this.cleanPath();
        return this.loadBalance(builder, context, new Target.HardCodedTarget(this.type, this.name, this.url));
    } else {
        if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
            this.url = "http://" + this.url;
        }

        String url = this.url + this.cleanPath();
        Client client = (Client)this.getOptional(context, Client.class);
        if (client != null) {
            if (client instanceof FeignBlockingLoadBalancerClient) {
                client = ((FeignBlockingLoadBalancerClient)client).getDelegate();
            }

            if (client instanceof RetryableFeignBlockingLoadBalancerClient) {
                client = ((RetryableFeignBlockingLoadBalancerClient)client).getDelegate();
            }

            builder.client(client);
        }

        this.applyBuildCustomizers(context, builder);
        Targeter targeter = (Targeter)this.get(context, Targeter.class);
        // 通过 Feign 类中的 Builder类 target 方法为其生成代理对象
        return targeter.target(this, builder, context, new Target.HardCodedTarget(this.type, this.name, url));
    }
}

上面代码中有两处比较重要的地方法, this.feign(context) 获取 Feign.builder() 客户端的配置targeter.target 方法生成代理对象;所以我们自定义的-Feign.builder() 客户端也只需要 完成这两个动作就可以;下面具体看下客户端的配置和代理类生成的逻辑;

2.2 客户端的配置设置:

protected Feign.Builder feign(FeignContext context) {
	// log 日志实现类的获取
 FeignLoggerFactory loggerFactory = (FeignLoggerFactory)this.get(context, FeignLoggerFactory.class);
    Logger logger = loggerFactory.create(this.type);
    // 链式调用 设置 编解码 ,contract 协议
    Feign.Builder builder = ((Feign.Builder)this.get(context, Feign.Builder.class)).logger(logger).encoder((Encoder)this.get(context, Encoder.class)).decoder((Decoder)this.get(context, Decoder.class)).contract((Contract)this.get(context, Contract.class));
    // 设置 系统默认的配置文件,客户端自定义的配置文件
    this.configureFeign(context, builder);
    return builder;
}

上面代码 主要是构建了Feign.Builder 对象,并通过 configureFeign 方法解析配置设置超时时间,拦截器等;

protected void configureFeign(FeignContext context, Feign.Builder builder) {
 FeignClientProperties properties = this.beanFactory != null ? (FeignClientProperties)this.beanFactory.getBean(FeignClientProperties.class) : (FeignClientProperties)this.applicationContext.getBean(FeignClientProperties.class);
   FeignClientConfigurer feignClientConfigurer = (FeignClientConfigurer)this.getOptional(context, FeignClientConfigurer.class);
   this.setInheritParentContext(feignClientConfigurer.inheritParentConfiguration());
   if (properties != null && this.inheritParentContext) {
       if (properties.isDefaultToProperties()) {
       		// feign 客户端定义的配置类解析
           this.configureUsingConfiguration(context, builder);
           // 系统默认的配置解析
           this.configureUsingProperties((FeignClientProperties.FeignClientConfiguration)properties.getConfig().get(properties.getDefaultConfig()), builder);
           // 配置的客户端解析
           this.configureUsingProperties((FeignClientProperties.FeignClientConfiguration)properties.getConfig().get(this.contextId), builder);
       } else {
           this.configureUsingProperties((FeignClientProperties.FeignClientConfiguration)properties.getConfig().get(properties.getDefaultConfig()), builder);
           this.configureUsingProperties((FeignClientProperties.FeignClientConfiguration)properties.getConfig().get(this.contextId), builder);
           this.configureUsingConfiguration(context, builder);
       }
   } else {
       this.configureUsingConfiguration(context, builder);
   }

}

这里进行简单的说明:

  • configureUsingConfiguration 解析通过 @FeignClient 配置的configuration 的配置类;

  • configureUsingProperties:解析feign 默认的配置 比如 default 配置:
    在这里插入图片描述

  • configureUsingProperties:最后一个解析通过配置文件定义给某个客户端的配置,这里的applicationname2 对应某个@FeignClient 定义的contextId 或者 name
    在这里插入图片描述
    在这里插入图片描述

2.3 代理类的生成:

最终通过Feign 下的 Builder 类中的 target 方法生成代理对象:这里只展示部分代码

public <T> T target(Target<T> target) {
	return this.build().newInstance(target);
}

public Feign build() {
    super.enrich();
    SynchronousMethodHandler.Factory synchronousMethodHandlerFactory = new SynchronousMethodHandler.Factory(this.client, this.retryer, this.requestInterceptors, this.responseInterceptor, this.logger, this.logLevel, this.dismiss404, this.closeAfterDecode, this.propagationPolicy, this.forceDecoding);
    ReflectiveFeign.ParseHandlersByName handlersByName = new ReflectiveFeign.ParseHandlersByName(this.contract, this.options, this.encoder, this.decoder, this.queryMapEncoder, this.errorDecoder, synchronousMethodHandlerFactory);
    return new ReflectiveFeign(handlersByName, this.invocationHandlerFactory, this.queryMapEncoder);
}
  • .build() 方法对代理类的一些属性进行配置 其中SynchronousMethodHandler 在我们需要调用远程服务时会 通过 invoke 方法组装http 请求 并最终发起http 请求访问;
  • newInstance(target) 生成代理对象并返回;

总结

本文通过Feign.builder() 自定义Feign 客户端,做到每个feign 客户端自身完全隔离。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/321306.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【GitHub项目推荐--AI 开源项目/涵盖 OCR、人脸检测、NLP、语音合成多方向】【转载】

今天为大家推荐一个相当牛逼的AI开源项目&#xff0c;当前 Star 3.4k&#xff0c;但是大胆预判&#xff0c;这个项目肯定要火&#xff0c;未来 Star 数应该可以到 10k 甚至 20k&#xff01; 着急的&#xff0c;可以到 GitHub 直接去看源码 传送门&#xff1a;https://github.c…

GNSS差分码偏差(DCB)原理学习与数据下载地址

一、DCB原理 GNSS差分码偏差&#xff08;DCB&#xff0c;Differential Code Bias&#xff09;是由不同类型的GNSS信号在卫星和接收机不同通道产生的时间延迟&#xff08;硬件延迟/码偏差&#xff09;差异&#xff0c;按照频率相同或者不同又可以细分为频内偏差&#xff08;例如…

电子电器架构车载软件 —— 集中化架构软件开发

电子电器架构车载软件 —— 集中化架构软件开发 我是穿拖鞋的汉子&#xff0c;魔都中坚持长期主义的汽车电子工程师。 老规矩&#xff0c;分享一段喜欢的文字&#xff0c;避免自己成为高知识低文化的工程师&#xff1a; 屏蔽力是信息过载时代一个人的特殊竞争力&#xff0c;任…

好物周刊#36:程序员简历

村雨遥的好物周刊&#xff0c;记录每周看到的有价值的信息&#xff0c;主要针对计算机领域&#xff0c;每周五发布。 一、项目 1. SmartDNS 一个运行在本地的 DNS 服务器&#xff0c;它接受来自本地客户端的 DNS 查询请求&#xff0c;然后从多个上游 DNS 服务器获取 DNS 查询…

从零开始复现BERT,并进行预训练和微调

从零开始复现BERT 代码地址&#xff1a;https://gitee.com/guojialiang2023/bert 模型 BERT 是一种基于 Transformer 架构的大型预训练模型&#xff0c;它通过学习大量文本数据来理解语言的深层次结构和含义&#xff0c;从而在各种 NLP 任务中实现卓越的性能。 核心的 BER…

InseRF: 文字驱动的神经3D场景中的生成对象插入

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

基于特征选择和机器学习的酒店客户流失预测和画像分析

基于特征选择和机器学习的酒店客户流失预测和画像分析 基于特征选择和机器学习的酒店客户流失预测和画像分析摘要1. 业务理解2. 数据理解和处理2.1 特征理解2.2 数据基本情况2.3 特征相关性分析 3. 酒店客户流失预测模型构建和评估3.1 支持向量机3.2 K-means聚类用户画像构建 4…

ssh协议以及操作流程

ssh协议 1.是一种安全通道协议 2.对通信数据进行了加密处理&#xff0c;用于远程管理 3.对数据进行压缩 在日常生活中&#xff0c;我们使用的是openssh openssh 服务名称&#xff1a;sshd 服务端主程序&#xff1a;/usr/sbin/sshd 服务端配置文件&#xff1a;/etc/ssh/sshd_con…

pytorch一致数据增强—异用增强

前作 [1] 介绍了一种用 pytorch 模仿 MONAI 实现多幅图&#xff08;如&#xff1a;image 与 label&#xff09;同用 random seed 保证一致变换的写法&#xff0c;核心是 MultiCompose 类和 to_multi 包装函数。不过 [1] 没考虑不同图用不同 augmentation 的情况&#xff0c;如&…

《工具录》dig

工具录 1&#xff1a;dig2&#xff1a;选项介绍3&#xff1a;示例4&#xff1a;其他 本文以 kali-linux-2023.2-vmware-amd64 为例。 1&#xff1a;dig dig 是域名系统&#xff08;DNS&#xff09;查询工具&#xff0c;常用于域名解析和网络故障排除。比 nslookup 有更强大的功…

MISGAN

MISGAN:通过生成对抗网络从不完整数据中学习 代码、论文、会议发表: ICLR 2019 摘要: 生成对抗网络(GAN)已被证明提供了一种对复杂分布进行建模的有效方法,并在各种具有挑战性的任务上取得了令人印象深刻的结果。然而,典型的 GAN 需要在训练期间充分观察数据。在本文中…

【数据结构 | 希尔排序法】

希尔排序法 思路ShellSort 思路 希尔排序法又称缩小增量法。希尔排序法的基本思想是&#xff1a;先选定一个整数&#xff0c;把待排序文件中所有记录分成个组&#xff0c;所有距离为的记录分在同一组内&#xff0c;并对每一组内的记录进行排序。然后&#xff0c;取&#xff0c…

Spark原理——Shuffle 过程

Shuffle 过程 Shuffle过程的组件结构 从整体视角上来看, Shuffle 发生在两个 Stage 之间, 一个 Stage 把数据计算好, 整理好, 等待另外一个 Stage 来拉取 放大视角, 会发现, 其实 Shuffle 发生在 Task 之间, 一个 Task 把数据整理好, 等待 Reducer 端的 Task 来拉取 如果更细…

【开发板资料】Arduino NANO 资料分享(引脚分布图、原理图、亮灯程序等)

给出部分参考资料以及来源 引脚分布 PINOUT 来源&#xff1a;Nano | Arduino Documentation https://docs.arduino.cc/hardware/nano PINOUT PINOUT 来源&#xff1a;https://www.tumblr.com/pighixxx/42591353009/arduino-nano-pinout https://www.tumblr.com/pighixxx/…

求解建公路问题

课程设计题目 求解建公路问题 课程设计目的 深入掌握 Prim 和 Kruskal算法在求解实际问题中的应用 问题描述 假设有 n 个村庄,编号从到,现在修建一些道路使任意两个村庄之间可以互相连通。所谓两个村庄 A 和B是连通的,指当且仅当A 和 B之间有一条道路或者存在一个村庄 C 使得…

UG装配-WAVE几何链接器

自上向下&#xff08;自顶向下&#xff09;设计 先将产品主要结构&#xff08;或主要部件&#xff09;建立好&#xff0c;然后再根据要求设计其它组件&#xff0c;使每个组件之间有数据关联&#xff0c;适用于产品开发初期&#xff0c;便于修改&#xff0c;修改组件数据后&…

如何利用小程序介绍公司品牌形象?

企业小程序的建设对于现代企业来说已经成为了一项必不可少的工作。随着移动互联网的快速发展&#xff0c;越来越多的职场人士和创业老板希望通过小程序来提升企业形象&#xff0c;增强与用户的互动&#xff0c;实现更好的商业效果。在这个过程中&#xff0c;使用第三方制作平台…

C-操作符详解

1.进制转换 1.1 10进制转2进制 方法&#xff1a;短除法 1.2 2进制转换8进制 8进制的数字每⼀位是0~7的&#xff0c;0~7的数字&#xff0c;各⾃写成2进制&#xff0c;最多有3个2进制位就⾜够了&#xff0c;⽐如7的⼆进制是111&#xff0c;所以在2进制转8进制数的时候&#xf…

三、Qt Creator 使用

关于Qt的安装及环境配置&#xff0c;在我的上一篇《二、QT下载、安装及问题解决(windows系统)》已经讲过了。 本章节有一个重点&#xff0c;在新建 工程文件时&#xff0c;所在路径不要有中文&#xff0c;否则编译及运行程序不能正常运行。 在使用Qt Creator&#xff08;以下…

A connection was successfully established with the server but then an error

在使用EFCore生成数据库的时候&#xff0c;报上面的错误&#xff01; 解决方法&#xff1a; 加&#xff08;EncryptTrue;TrustServerCertificateTrue;&#xff09;即可&#xff1a; "ConnectionStrings": { "DefaultConnection": "Data SourceLAP…
最新文章