Spring Cloud Feign 使用对象参数的操作

网友投稿 518 2022-08-26


Spring Cloud Feign 使用对象参数的操作

目录概述@RequestBody@SpringQueryMapQueryMapEncoder解决方案

概述

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

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

@RequestBody

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

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

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

@SpringQueryMap

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

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

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

QueryMapEncoder

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

FieldQueryMapEncoderBeanQueryMapEncoder

虽然这两个实现不能满足我的要求,但是只要稍加修改写一个自己的实现类就行了,于是我在 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&gthttp://; 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);

}

加注释的http://方法,就是我后添加进去的。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;

@Configuration

public 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") LonmqlNKg id);

}


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:手把手教你用Pytorch训练图像分类网络(pytorch实现CNN)
下一篇:Python海龟作图(python海龟作图画爱心)
相关文章

 发表评论

暂时没有评论,来抢沙发吧~