布景

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

@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办法的回来值,它的实际类型有四种基本状况(其他都是这四种的排列组合),一种是不带泛型的类,一种带固定泛型的类,一种是不固定的T类型的泛型,最终一种是带?的分别如下

Feign返回值统一处理

Feign返回值统一处理

Feign返回值统一处理

Feign返回值统一处理

本文只讨论前面两种类型,后边两种类型实际传到Decode中是无法知道实际类型的,除非经过某种办法把回来的实际类型传到Decode中(比方ThreadLocal、办法参数、恳求头等等),或者泛型是有上界的,如<T ? extends UpUser>,那么能够经过typegetBounds()办法获取到上界类型,进行序列化。不然无法确认类型进行反序列化。(或者我自己想的一种思路,相似懒加载的方式,不知道能不能完成,便是延迟序列化,等用到的时候确认类型了再序列化)

现在便是要将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即可,恳求就没有参数泛型的问题了。