北京网站建设 合一百度搜索首页
背景
服务端的接口一般有固定的返回格式,有数据、返回码和异常时错误信息。结构如下
@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类型的泛型,最后一种是带?的分别如下
本文只讨论前面两种类型,后面两种类型实际传到
Decode
中是没法知道实际类型的,除非通过某种方法把返回的实际类型传到Decode
中(比如ThreadLocal、方法参数、请求头等等),或者泛型是有上界的,如<T ? extends UpUser>
,那么可以通过type
的getBounds()
方法获取到上界类型,进行序列化。否则无法确定确定类型的,进行反序列化。
现在就是要将Response中的返回值转换成BaseResponse
类型,而且是包括BaseResponse
里面T这个泛型的,如果T中还带了泛型,不论嵌套几层都需要转换好,这样调用地方可以直接使用。我使用的序列化工具是Gson(ObjectMapper也是类似的)。
带泛型的转换其实是有现有的方法可以直接转的,但是这里有点难处理的是,将BaseResponse
类型和参数中的Type
合并成一个,作为参数传到Gson
的fromJson
方法中,查看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就可以*/@Overridepublic Type[] getActualTypeArguments() {Type[] types = new Type[1];types[0] = type;return types;}/*** 最外层的类型就是我们要与type合并的BaseResponse类型*/@Overridepublic Type getRawType() {return BaseResponse.class;}/*** 这个Owner一般没用到,如果type是个内部类静态类情况下,需要返回最外部的类型,这里直接调用Class对象获取封闭类的方法*/@Overridepublic 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();@Overridepublic 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
即可,请求就没有参数泛型的问题了。