Feign返回值统一处理

背景

服务端的接口一般有固定的返回格式,有数据、返回码和异常时错误信息。结构如下

@Data
public class BaseResponse<T> {

    private String code;

    private String message;

    private T data;
    
    public boolean isSuccess() {
        return "SUCCESS".equals(code);
    }
}
复制代码

正常情况下我们只关注里面的data字段。不做任何处理情况下,需要将BaseResponse类型作为Feign Client方法的返回值,然后在调用Feign的业务代码处手动调用getData()方法来获取数据。这种重复的代码可以抽出来统一处理(请求数据也类似)。

解决方案

使用自定义Decoder来统一处理,重写Object decode(Response response, Type type)方法,其中Response 就是被调用接口返回的响应,Type就是Feign Client方法的返回值,它的实际类型有两种情况,一种带泛型的类,另外一种是不带泛型的类,分别如下

现在就是要将Response中的返回值转换成BaseResponse类型,而且是包括BaseResponse里面T这个泛型的,如果T中还带了泛型,不论嵌套几层都需要转换好,这样调用地方可以直接使用。我使用的序列化工具是Gson(ObjectMapper也是类似的)。

带泛型的转换其实是有现有的方法可以直接转的,但是这里有点难处理的是,将BaseResponse类型和参数中的Type合并成一个,作为参数传到GsonfromJson方法中,查看Type类的实现类,发现有一个ParameterizedType接口,这个就是描述了对象的参数类型。每个方法说明如下

public interface ParameterizedType extends Type {
    /**
     * 返回里面的泛型,比如List<String>, 那么这个方法返回String,如果是Map<String, Integer>那么这个方法返回{String, Integer}的数组
     * @since 1.5
     */
    Type[] getActualTypeArguments();

    /**
     * 返回当前这个类的类型,比如List<String>, 那么这个方法返回List,如果是Map<String, Integer>那么这个方法返回Map
     */
    Type getRawType();

    /**
     * 如果是内部类的情况,这个方法返回的是最外层的类,也就是封闭类,比如O<T>.I<S>这种类型,返回的是O<T>
     */
    Type getOwnerType();
}
复制代码

要注意一点,Class对象也是实现了Type接口的。

ParameterizedType接口解决了参数合并的问题,自定一个参数类型类,实现这三个方法

import sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

public class MyParameterizedType implements ParameterizedType {

    private Type type;

    /**
     * 将Feign Client方法的返回值注入,只要两种类型,一种是ParameterizedTypeImpl,另一种是具体的Class对象
     */
    public MyParameterizedType(Type type) {
        this.type = type;
    }

    /**
     * 属性Type就是BaseResponse的泛型类型,直接返回type就可以
     */
    @Override
    public Type[] getActualTypeArguments() {
        Type[] types = new Type[1];
        types[0] = type;
        return types;
    }

    /**
     * 最外层的类型就是我们要与type合并的BaseResponse类型
     */
    @Override
    public Type getRawType() {
        return BaseResponse.class;
    }

    /**
     * 这个Owner一般没用到,如果type是个内部类静态类情况下,需要返回最外部的类型,这里直接调用Class对象获取封闭类的方法
     */
    @Override
    public Type getOwnerType() {
        if (type instanceof ParameterizedTypeImpl) {
            ParameterizedTypeImpl typeImpl = (ParameterizedTypeImpl) type; 
            return typeImpl.getRawType().getEnclosingClass();
        }
        
        if (type instanceof Class) {
            return ((Class) type).getEnclosingClass();
        }
        
        return null;
    }
}

复制代码

这样序列化问题就能解决了,现在只要编写Decoder类就可以了。

import com.google.gson.Gson;
import feign.FeignException;
import feign.Response;
import feign.codec.Decoder;

import java.io.IOException;
import java.lang.reflect.Type;

public class MyDecode implements Decoder {

    private Gson gson = new Gson();

    @Override
    public Object decode(Response response, Type type) throws FeignException, IOException {

        MyParameterizedType myType = new MyParameterizedType(type);

        BaseResponse baseResponse = gson.fromJson(response.body().asReader(), myType);

        if (type instanceof BaseResponse) {
            return baseResponse;
        }

        if (baseResponse.isSuccess()) {
            return baseResponse.getData();
        }
        
        throw new RuntimeException("返回异常");
    }
}
复制代码

这里加了一个BaseResponse判断,如果需要返回整个数据,比如根据BaseResponse的返回码做业务逻辑,就可以在Feign Client的方法返回值直接写带泛型的BaseResponse类型。也加了一个统一的校验,如果要获取数据,需要返回码是正常才行。

总结

这种写法优点就是一次性反序列化到位,后续使用根据泛型里面的类型直接使用,如果不进行泛型合并,只转成BaseResponse类型,如果data的类型是有很多泛型嵌套的,那么可能反序列化类型是有问题的,比如data的类型是List<User>,那么不指定详细的泛型类型,直接转成BaseResponse类型,那么data字段序列化结果会是List<Map<String, String>,没法直接使用的。

关于参数化合并问题,这种思路可以借鉴,运用到其他场景。还有像请求数据统一封装其实也是类似,自定义一个Encoder即可,请求就没有参数泛型的问题了。

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值