解决Feign的自定义解码器在接口返回值为void时不执行的问题

news2025/1/12 9:37:56

项目的接口有一个全局的响应包装器,将接口的所有返回,包括各种类型如List、Entity,或者void,以及抛出的异常,封装成统一的结构给到前端,所以在使用Feign发起远程调用的时候,需要一个自定义的解码器,来对正常的返回值做一个拆包处理,同时对可能返回的异常重新抛出

public class FeignDecoder implements Decoder {
    @Override
    public Object decode(Response response, Type type) throws IOException {
        String result = Util.toString(response.body().asReader(Util.UTF_8));
        CommonResult commonResult = JSON.parseObject(result, CommonResult.class);
        if (commonResult.getCode() != ResultCode.SUCCESS.getCode()) {
            throw new RuntimeException(commonResult.getMessage());
        }
        return JSON.parseObject(JSON.toJSONString(commonResult.getData()), type);
    }
}

实际运行过程中,发现对于有返回值的方法,不管是正常响应,还是异常都可以正常处理,但是对于@FeiginClient中,返回值是void的方法,即使对方接口抛出了异常,客户端依然会收到success,经过排查发现,调用无返回值方法时,Feign的自定义解码器没有执行

通过debug查看接口的调用栈,发现接口在执行的时候,会经过一个AsyncResponseHandler的类处理,然后执行一个判断是否为空的方法isVoidType,目测跟我们遇到的问题有关联
在这里插入图片描述

进入AsyncResponseHandler,找到这个方法

@Experimental
class AsyncResponseHandler {
	......
	......
	void handleResponse(CompletableFuture<Object> resultFuture, String configKey, Response response, Type returnType, long elapsedTime) {
		......
		......
		if (response.status() >= 200 && response.status() < 300) {
    		if (this.isVoidType(returnType)) {
        		resultFuture.complete((Object)null);
    		} else {
        		result = this.decode(response, returnType);
       	 		shouldClose = this.closeAfterDecode;
        		resultFuture.complete(result);
    		}
		} 
		......
		......
	}
}

如果返回类型是void,就直接跳过了,没有走解码流程

初步确定了问题点,继续在debug的调用栈中回溯,找这个方法调用的地方,在一个SynchronousMethodHandler的类中

final class SynchronousMethodHandler implements MethodHandler {
	......
	Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
		......
		this.asyncResponseHandler.handleResponse(resultFuture, this.metadata.configKey(), response, this.metadata.returnType(), elapsedTime);
		......
	}
	......
}

通过观察这个类,在它的构造方法中,发现了一个forceDecoding变量,顾名思义“强制解码”,散发着非常可疑的气息

    private SynchronousMethodHandler(Target<?> target, Client client, Retryer retryer, List<RequestInterceptor> requestInterceptors, Logger logger, Level logLevel, MethodMetadata metadata, feign.RequestTemplate.Factory buildTemplateFromArgs, Options options, Decoder decoder, ErrorDecoder errorDecoder, boolean decode404, boolean closeAfterDecode, ExceptionPropagationPolicy propagationPolicy, boolean forceDecoding) {
        this.target = (Target)Util.checkNotNull(target, "target", new Object[0]);
        this.client = (Client)Util.checkNotNull(client, "client for %s", new Object[]{target});
        this.retryer = (Retryer)Util.checkNotNull(retryer, "retryer for %s", new Object[]{target});
        this.requestInterceptors = (List)Util.checkNotNull(requestInterceptors, "requestInterceptors for %s", new Object[]{target});
        this.logger = (Logger)Util.checkNotNull(logger, "logger for %s", new Object[]{target});
        this.logLevel = (Level)Util.checkNotNull(logLevel, "logLevel for %s", new Object[]{target});
        this.metadata = (MethodMetadata)Util.checkNotNull(metadata, "metadata for %s", new Object[]{target});
        this.buildTemplateFromArgs = (feign.RequestTemplate.Factory)Util.checkNotNull(buildTemplateFromArgs, "metadata for %s", new Object[]{target});
        this.options = (Options)Util.checkNotNull(options, "options for %s", new Object[]{target});
        this.propagationPolicy = propagationPolicy;
        if (forceDecoding) {
            this.decoder = decoder;
            this.asyncResponseHandler = null;
        } else {
            this.decoder = null;
            this.asyncResponseHandler = new AsyncResponseHandler(logLevel, logger, decoder, errorDecoder, decode404, closeAfterDecode);
        }

    }

这个forceDecoding默认是false,所以走了else分支,创建了一个AsyncResponseHandler

而这个AsyncResponseHandler就是导致接口返回void时,自定义解码器不执行的罪魁祸首

所以猜测如果把forceDecoding改为true,应该是能解决这个问题的

经过一番回溯,最终在Feign的内部类Builder里找到了这个属性

public abstract class Feign {
	......
	......
	public static class Builder {
        private final List<RequestInterceptor> requestInterceptors = new ArrayList();
        private Level logLevel;
        private Contract contract;
        private Client client;
        private Retryer retryer;
        private Logger logger;
        private Encoder encoder;
        private Decoder decoder;
        private QueryMapEncoder queryMapEncoder;
        ......
        ......
        private boolean forceDecoding;
        ......
        ......
    }
}

这个属性在它的构造方法里默认设置为false

	public Builder() {
        this.logLevel = Level.NONE;
        this.contract = new Default();
        this.client = new feign.Client.Default((SSLSocketFactory)null, (HostnameVerifier)null);
        this.retryer = new feign.Retryer.Default();
        this.logger = new NoOpLogger();
        this.encoder = new feign.codec.Encoder.Default();
        this.decoder = new feign.codec.Decoder.Default();
        this.queryMapEncoder = new FieldQueryMapEncoder();
        this.errorDecoder = new feign.codec.ErrorDecoder.Default();
        this.options = new Options();
        this.invocationHandlerFactory = new feign.InvocationHandlerFactory.Default();
        this.closeAfterDecode = true;
        this.propagationPolicy = ExceptionPropagationPolicy.NONE;
        this.forceDecoding = false;
        this.capabilities = new ArrayList();
    }

然后找了下,只有一个非public方法forceDecoding可以将它改为true

在这里插入图片描述

按理说这个属性应该是可以通过配置文件传入的,不过并没有找到相关的读取入口,而且这个方法有意地没有像其他方法一样设置为public,不再纠结,看到这里便已经有可行的解决办法了

构建一个Feign.Builder,通过反射将该属性设置为true

@Configuration
public class FeignConfig {
    ......
    ......
    @Bean
    public FeignDecoder feignDecoder() {
        return new FeignDecoder();
    }

    @Bean
    public Feign.Builder feignBuilder(FeignDecoder decoder) throws Exception {
        Feign.Builder builder = Feign.builder().decoder(decoder);
        Field forceDecoding = builder.getClass().getDeclaredField("forceDecoding");
        forceDecoding.setAccessible(true);
        forceDecoding.set(builder, true);
        return builder;
    }
}

再次测试,@FeignClient里返回void的方法也可以进入解码器了

最后对解码器做一些调整,返回值是void就只判断响应码是否有异常,不做进一步的拆包处理

public class FeignDecoder implements Decoder {

    @Override
    public Object decode(Response response, Type type) throws IOException {
        String result = Util.toString(response.body().asReader(Util.UTF_8));
        CommonResult commonResult = JSON.parseObject(result, CommonResult.class);
        if (commonResult.getCode() != ResultCode.SUCCESS.getCode()) {
            throw new AicRuntimeException(ResultCode.RPC_FAILED, commonResult.getMessage());
        }
        if (Void.class == type || Void.TYPE == type) {
            return null;
        }
        return JSON.parseObject(JSON.toJSONString(commonResult.getData()), type);
    }
}

问题解决

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

详解 ElasticSearch 基础教程

&#x1f339; 分享 ElasticSearch 基础&#xff0c;请指教。&#x1f339;&#x1f339; 如你对技术也感兴趣&#xff0c;欢迎交流。&#x1f339;&#x1f339;&#x1f339; 如有对阁下帮助&#xff0c;请&#x1f44d;点赞&#x1f496;收藏&#x1f431;‍&#x1f3cd;分…

Python 自动化Web测试

限于作者水平有限&#xff0c;以下内容可能是管窥之见&#xff0c;希望大家高抬贵手&#xff0c;且让我斗胆抛砖引玉。 公司产品迪备主要是通过网页操作来进行数据库的备份与恢复&#xff0c;监控与管理&#xff0c;因此在测试的过程中&#xff0c;可以用python测试脚本来模拟…

运维知识点汇总

一.公共基础 linux常用目录 链接一 链接二 linux系统启动 链接一 链接二 LVM 链接一 磁盘挂载 链接一 文件权限 链接一 二.VLAN详解 链接 三.中间件 单体部署&#xff1a; 优点&#xff1a; &#xff08;1&#xff09;小团队成型即可完成开发-测试-上线&am…

3D机器视觉:解锁未来的立体视野

原创 | 文 BFT机器人 机器视觉领域一直在不断演进&#xff0c;从最初的二维图像处理&#xff0c;逐渐扩展到了更复杂的三维领域&#xff0c;形成了3D机器视觉。3D机器视觉技术的涌现为计算机系统带来了全新的感知和理解能力&#xff0c;这一领域的发展正日益受到广泛关注。本文…

Android系统为什么采用Binder作为IPC机制

Android系统提供了多种进程间通信&#xff08;IPC&#xff09;的机制&#xff0c;用于不同进程之间的数据交换和通信。以下是Android系统中常用的几种IPC机制&#xff1a; Intent&#xff1a;Intent是Android系统中常用的一种进程间通信方式。通过发送Intent&#xff0c;可以在…

链表去重Java

去除掉链表中重复的元素,两种方法: static class ListNode{private int val;private ListNode next;public ListNode(int val, ListNode next) {this.val val;this.next next;}Overridepublic String toString() {return "ListNode{" "val" val ",…

【jvm】程序计数器

目录 一、介绍二、作用三、示例3.1 代码3.2 javap -v Test1.class3.2.1 操作指令及地址 一、介绍 1.jvm中的程序计数寄存器中&#xff08;program counter register&#xff09;&#xff0c;register的命名源于cpu的寄存器&#xff0c;寄存器存储指令相关的现场信息 2.cpu只有把…

C++学习之指针和数组

指针和一维数组 一个数组包含若干元素&#xff0c;每个数组元素都在内存中占用存储单元&#xff0c;它们都有相应的地址。指针变量既可以指向变量&#xff0c;当然也可以指向数组元素。所谓数组元素的指针就是数组元素的地址。 eg&#xff1a; int a[6]; //定义一个整数数组a…

web:[HCTF 2018]admin

题目 点击页面显示如下 点击hctf显示 没有账号&#xff0c;先注册一个 随便注册一个试试看 输入账号密码显示如下 页面没有其他的提示&#xff0c;查看源代码 这里提示不是admin 先注册一个admin账号试一下 显示admin已经被注册了&#xff0c;所以知道用户名为admin&#xff0…

【图像分割】SAM、FastSAM与MobileSAM原理

文章目录 前言&#xff1a;Segment Anything一、FastSAM二、MobileSAM框架实验 总结 前言&#xff1a;Segment Anything Meta 今年发布了图像分割模型 Segment Anything Model (SAM) 。SAM 已经学会了关于物体的一般概念&#xff0c;可以为任何图像或视频中的任何物体生成 mas…

基于共生生物优化的BP神经网络(分类应用) - 附代码

基于共生生物优化的BP神经网络&#xff08;分类应用&#xff09; - 附代码 文章目录 基于共生生物优化的BP神经网络&#xff08;分类应用&#xff09; - 附代码1.鸢尾花iris数据介绍2.数据集整理3.共生生物优化BP神经网络3.1 BP神经网络参数设置3.2 共生生物算法应用 4.测试结果…

JDBC-day03(BLOB类型字段,批量插入)

四&#xff1a;操作BLOB类型字段 1.MySQL BLOB类型 在MySQL中&#xff0c;BLOB是一个二进制大型对象&#xff0c;是一个可以存储大量数据的容器&#xff0c;它能容纳不同大小的数据。可以用来存储图片&#xff0c;视频等 插入BLOB类型的数据必须使用PreparedStatement&#x…

redis,mongoDB,mysql,Elasticsearch区别

Redis&#xff1a; Redis是一种高性能键值存储数据库&#xff0c;基于内存操作&#xff0c;支持数据持久化&#xff0c;支持数据类型丰富灵活&#xff0c;如字符串、哈希、列表、集合、有序集合等。Redis还提供了订阅/发布、事务、Lua脚本、主从同步等功能&#xff0c;适用于访…

学习记忆——数学篇——案例——代数——函数——一元二次函数

记忆宫殿法 一元二次函数&#xff1a; y a x 2 b x c yax^2bxc yax2bxc a &#xff1e; 0 a&#xff1e;0 a&#xff1e;0&#xff0c;开口向上&#xff1b; a &#xff1c; 0 a&#xff1c;0 a&#xff1c;0&#xff0c;开口向下&#xff1b; x − b 2 a x-\frac{b}{2a…

2023-10-09 python-使用psd_tools-读取psd信息及导出图层图片-记录

摘要: 2023-10-09 python-使用psd_tools-读取psd信息及导出图层图片-记录 相关文档: Usage — psd-tools 1.9.28 documentation 读取psd的信息: py代码: from psd_tools import PSDImagepsd PSDImage.open(example.psd) #psd PSDImage.open(one.psd)print(psd)for layer in …

DirectX C++项目调试时报错:The FX file cannot be complied.

文章目录 遇到的问题错误排除方法经验总结 遇到的问题 在编虚拟现实的课程作业的时候&#xff0c;打算基于上一次的作业项目改一改来交&#xff0c;于是把上次项目的代码复制过来。生成解决方案的过程没有报错&#xff0c;但是在调试该项目时&#xff0c;报错内容如下图所示&a…

vulnhub_clover靶机渗透测试

clover靶机 文章目录 clover靶机信息收集ftp渗透web渗透横线移动权限提升靶机总结 靶机地址&#xff1a;https://www.vulnhub.com/entry/clover-1,687/ 信息收集 使用nmap扫描得到了很对端口&#xff0c;能用的也就是21 22 80三个端口&#xff0c;其他都是关闭的&#xff0c;全…

【开发篇】二十三、SpringBoot Admin端点指标控制以及自定义端点

文章目录 1、info端点指标控制2、health端点指标控制3、metrics端点指标控制4、自定义端点5、补充 接上篇&#xff0c;整合完SpringBoot Admin的客户端和服务端后&#xff0c;在监控页面看到信息栏是空的&#xff0c;但info端点是开放的&#xff0c;这就涉及到端点指标控制。 1…

基于ffmpeg给视频添加时间字幕

FFmpeg是一套可以用来记录、转换数字音频、视频&#xff0c;并能将其转化为流的开源计算机程序&#xff0c;我们可以基于ffmpeg对视频进行各种操作。本文主要介绍基于ffmpeg给视频添加字幕&#xff0c;字幕的内容为视频所播放的时间&#xff08;故需要安装ffmpeg&#xff0c;具…

【配置vscode编写python代码并输出到外部控制台】

配置vscode编写python代码并输出到外部控制台 1、扩展中添加python插件 2、打开一个文件夹&#xff0c;在里面新建一个.py文件&#xff0c;粘贴print(‘你好啊&#xff01;’)并运行 运行结果如下: 3、点击调试点击如下图 生成launch.json&#xff0c;将console后面改成exte…