布景
服务端的接口一般有固定的回来格局,有数据、回来码和异常时错误信息。结构如下
@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就能够
*/
@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
即可,恳求就没有参数泛型的问题了。