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
合并成一个,作为参数传到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
即可,请求就没有参数泛型的问题了。
转载自:https://juejin.cn/post/7214699255508615229