千家信息网

Spring Cloud Feign使用对象参数的方法

发表于:2025-02-05 作者:千家信息网编辑
千家信息网最后更新 2025年02月05日,本文小编为大家详细介绍"Spring Cloud Feign使用对象参数的方法",内容详细,步骤清晰,细节处理妥当,希望这篇"Spring Cloud Feign使用对象参数的方法"文章能帮助大家解决
千家信息网最后更新 2025年02月05日Spring Cloud Feign使用对象参数的方法

本文小编为大家详细介绍"Spring Cloud Feign使用对象参数的方法",内容详细,步骤清晰,细节处理妥当,希望这篇"Spring Cloud Feign使用对象参数的方法"文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。

概述

Spring Cloud Feign 用于微服务的封装,通过接口代理的实现方式让微服务调用变得简单,让微服务的使用上如同本地服务。但是它在传参方面不是很完美。在使用 Feign 代理 GET 请求时,对于简单参数(基本类型、包装器、字符串)的使用上没有困难,但是在使用对象传参时却无法自动的将对象包含的字段解析出来。

如果你没耐心看完,直接跳到最后一个标题跟着操作就行了。

@RequestBody

对象传参是很常见的操作,虽然可以通过一个个参数传递来替代,但是那样就太麻烦了,所以必须解决这个问题。

我在网上看到有人用 @RequestBody 来注解对象参数,我在尝试后发现确实可用。这个方案实际使用 body 体装了参数(使用的是 GET 请求),但是这个方案有些问题:

  1. 注解需要在 consumer 和 provider 两边都有,这造成了麻烦

  2. 使用接口测试工具 Postman 无法跑通微服务,后来发现是因为 body 体的格式选择不正确,这个格式不是通常的表单或者路径拼接,而是 GraphQL。我没有研究过这种格式应该如何填写参数,但是 Postman 上并没有给出像表单那样方便的格式,这对于测试是很不利的。

@SpringQueryMap

于是我继续寻找答案,发现可以使用 @SpringQueryMap 仅添加在 consumer 的参数上就能自动对 Map 类型参数编码再拼接到 URL 上。而我用的高版本的 Feign,可以直接把对象编码。

可是正当我以为得到正解时,却发现还是有问题:

我明明在 Date 类型的字段上加上了 @DateTimeFormat(pattern = "yyyy-MM-dd"),却没有生效,他用自己的方式进行了编码(或者说序列化),而且官方确实没有提供这种格式化方式。

又一番找寻后发现了一位大佬自己实现了一个注解转换替代 @SpringQueryMap,并实现了丰富的格式化功能 ORZ,只能说佩服佩服。但是我没有那样的技术,又不太想复制粘贴他那一大堆的代码,因为出了问题也不好改,所以我还是想坚持最大限度地使用框架,最小限度的给框架填坑。

QueryMapEncoder

终于功夫不费有心人,我发现了 Feign 预留的自定义编码器接口 QueryMapEncoder,框架提供了两个实现:

  • FieldQueryMapEncoder

  • BeanQueryMapEncoder

虽然这两个实现不能满足我的要求,但是只要稍加修改写一个自己的实现类就行了,于是我在 FieldQueryMapEncoder 的基础上修改,仅仅添加了一个方法,小改了一个方法就实现了功能。

原理:Feign 其实还是用 Map 进行的编码,编码方式也很简单,String 是 key,Object 是 value。最开始的方式就是用 Object 的 toString() 方法把参数编码,这也是为什么 Date 字段会变成一个默认的时间格式,因为 toString() 根本和 @DateTimeFormat 没有关系。而高版本使用编码器实现了对象传参,实际实际上是通过简单的反射获取对象的元数据,再放到 Map 中。

上面的原理都能从 @DateTimeFormat 的注释和编码器的源码中得到答案。

我们要做的就是自定义一个编码器,实现在元数据放入 Map 之前根据需要把字段变成我们想要的字符串。下面是我实现的代码,供参考:

package com.example.billmanagerfront.config.encoder;import java.lang.reflect.Field;import java.text.DateFormat;import java.text.SimpleDateFormat;import java.util.ArrayList;import java.util.Collections;import java.util.List;import java.util.Map;import java.util.Optional;import java.util.TimeZone;import java.util.concurrent.ConcurrentHashMap;import java.util.stream.Collectors;import org.springframework.format.annotation.DateTimeFormat;import feign.Param;import feign.QueryMapEncoder;import feign.codec.EncodeException;public class PowerfulQueryMapEncoder implements QueryMapEncoder {    private final Map, ObjectParamMetadata> classToMetadata = new ConcurrentHashMap<>();    @Override    public Map encode(Object object) throws EncodeException {        ObjectParamMetadata metadata = classToMetadata.computeIfAbsent(object.getClass(),                ObjectParamMetadata::parseObjectType);        return metadata.objectFields.stream()                .map(field -> this.FieldValuePair(object, field))                .filter(fieldObjectPair -> fieldObjectPair.right.isPresent())                .collect(Collectors.toMap(this::fieldName, this::fieldObject));    }    private String fieldName(Pair> pair) {        Param alias = pair.left.getAnnotation(Param.class);        return alias != null ? alias.value() : pair.left.getName();    // 可扩展为策略模式,支持更多的格式转换    private Object fieldObject(Pair> pair) {        Object fieldObject = pair.right.get();        DateTimeFormat dateTimeFormat = pair.left.getAnnotation(DateTimeFormat.class);        if (dateTimeFormat != null) {            DateFormat format = new SimpleDateFormat(dateTimeFormat.pattern());            format.setTimeZone(TimeZone.getTimeZone("GMT+8")); // TODO: 最好不要写死时区            fieldObject = format.format(fieldObject);        } else {        }        return fieldObject;    private Pair> FieldValuePair(Object object, Field field) {        try {            return Pair.pair(field, Optional.ofNullable(field.get(object)));        } catch (IllegalAccessException e) {            throw new EncodeException("Failure encoding object into query map", e);    private static class ObjectParamMetadata {        private final List objectFields;        private ObjectParamMetadata(List objectFields) {            this.objectFields = Collections.unmodifiableList(objectFields);        private static ObjectParamMetadata parseObjectType(Class type) {            List allFields = new ArrayList();            for (Class currentClass = type; currentClass != null; currentClass = currentClass.getSuperclass()) {                Collections.addAll(allFields, currentClass.getDeclaredFields());            }            return new ObjectParamMetadata(allFields.stream()                    .filter(field -> !field.isSynthetic())                    .peek(field -> field.setAccessible(true))                    .collect(Collectors.toList()));    private static class Pair {        private Pair(T left, U right) {            this.right = right;            this.left = left;        public final T left;        public final U right;        public static  Pair pair(T left, U right) {            return new Pair<>(left, right);}

加注释的方法,就是我后添加进去的。encode 方法的最后一行稍微修改了一下,引用了我加的方法,其他都是直接借鉴过来的(本来我想更偷懒,直接继承一下子,但是它用了私有的内部类导致我只能全部复制粘贴了)。

解决方案

1.不用引入其他的 Feign 依赖,保证有下面这个就行(看网上其他方法还要引入特定依赖,要对应版本号,挺麻烦的)

    org.springframework.cloud    spring-cloud-starter-openfeign

2.编写上面那样的类,你可以直接复制过去改个包名就行,如果还需要除了 Date 以外的格式化,请看注释和文章分析。其中我对日期的格式化,直接使用了 @DateTimeFormat 提供的模式,和 Spring 保持了一致。

3.编写一个 Feign 配置类,将刚自定义的编码器注册进去。细节我就不多说了:

package com.example.billmanagerfront.config;import com.example.billmanagerfront.config.encoder.PowerfulQueryMapEncoder;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import feign.Feign;import feign.Retryer;@Configurationpublic class FeignConfig {    @Bean    public Feign.Builder feignBuilder() {        return Feign.builder()                .queryMapEncoder(new PowerfulQueryMapEncoder())                .retryer(Retryer.NEVER_RETRY);    }}

4.Feign 代理接口中声明使用这个配置类,细节不谈

package com.example.billmanagerfront.client;import java.util.List;import com.example.billmanagerfront.config.FeignConfig;import com.example.billmanagerfront.pojo.Bill;import com.example.billmanagerfront.pojo.BillType;import org.springframework.cloud.openfeign.FeignClient;import org.springframework.cloud.openfeign.SpringQueryMap;import org.springframework.web.bind.annotation.DeleteMapping;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PathVariable;@FeignClient(name = "BILL-MANAGER", path = "bill", configuration = FeignConfig.class)public interface BillClient {    @GetMapping("list")    List list(@SpringQueryMap(true) Bill b);    @GetMapping("type")    List type();    @DeleteMapping("delete/{id}")    public String delete(@PathVariable("id") Long id);}

读到这里,这篇"Spring Cloud Feign使用对象参数的方法"文章已经介绍完毕,想要掌握这篇文章的知识点还需要大家自己动手实践使用过才能领会,如果想了解更多相关内容的文章,欢迎关注行业资讯频道。

参数 编码 对象 方法 格式 方式 编码器 服务 字段 接口 文章 问题 实际 就是 方案 框架 注解 版本 类型 细节 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 中国网络安全人员是谁 数据库什么最好 政府出台的网络安全政策 潜渊症服务器怎么加模组 天津信息化软件开发服务标准 数据库技术所具备特点 服务器网卡在哪插 点牛网络技术公司客服电话 网络安全 画 邯郸电商软件开发 香河廊坊erp网络技术 虹口区本地软件开发发展 广东省公安局网络安全监督检查 如何找到表格里丢失的数据库 软件开发在家可以做吗 2020互联网科技战疫论坛 数据库时间范围用什么空间索引 校园网络安全大使张雅琦 新盗墓笔记潘家园服务器 游戏如何连接上服务器 支付软件开发款账务处理 计算机应用软件开发l论文 天津信息化软件开发服务标准 学校当前网络安全存在的突出问题 电商erp软件开发多少钱 中国互联网科技人哪里人 如何看待当今网络安全问题 北京停车场智能软件开发价位 国内的专利数据库有哪些 东方财富服务器功能哪里开启
0