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方法的返回值,它的实际类型有四种基本情况(其他都是这四种的排列组合),一种是不带泛型的类,一种带固定泛型的类,一种是不固定的T类型的泛型,最后一种是带?的分别如下
不带泛型
带泛型
在这里插入图片描述
在这里插入图片描述

本文只讨论前面两种类型,后面两种类型实际传到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类型。也加了一个统一的校验,如果要获取数据,需要返回码是正常才行。

ObjectMapper泛型嵌套反序列化

下面再提供ObjectMapper如何处理上面复杂泛型的序列化(也只是前面两种情况泛型中类型确定的情况)

public class MyDecode implements Decoder {

    private ObjectMapper objectMapper = new ObjectMapper();

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

        TypeFactory typeFactory = objectMapper.getTypeFactory();

        BaseResponse baseResponse = objectMapper.readValue(response.body().asInputStream(),
                typeFactory.constructParametricType(BaseResponse.class, typeFactory.constructType(type)));

        if (type instanceof BaseResponse) {
            return baseResponse;
        }

        if (baseResponse.isSuccess()) {
            return baseResponse.getData();
        }

        throw new RuntimeException("返回异常");
    }
}

重点是通过TypeFactory将参数中的Type转成JavaTypeJavaTypeParameterizedType类似,表示复杂泛型的一种数据结构),再通过TypeFactoryconstructParametricType方法合并BaseResponseJavaType

总结

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

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

  • 3
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
在使用Feign进行接口调用时,我们可以通过统一处理返回值来简化代码。具体来说,我们可以定义一个类或接口来处理返回值。该类或接口可以使用泛型来适应不同的返回值类型。 首先,我们可以定义一个统一的接口来处理返回值,例如`ResponseProcessor`。该接口可以使用泛型来适应不同的返回值类型。我们可以在接口中定义一个方法来对返回值进行处理。 ```java public interface ResponseProcessor<T> { void process(T response); } ``` 接下来,我们可以创建一个公共的处理类,例如`FeignResponseHandler`,该类实现了`ResponseProcessor`接口,并根据实际情况对返回值进行处理。例如,我们可以在`process`方法中打印返回值或进行其他操作。这样,我们就可以在需要处理返回值的地方直接使用该处理类。 ```java public class FeignResponseHandler<T> implements ResponseProcessor<T> { @Override public void process(T response) { // 对返回值进行处理,例如打印返回值 System.out.println("处理返回值:" + response.toString()); } } ``` 最后,我们可以在调用Feign接口的地方使用这个处理类。在调用的时候,我们可以将处理类作为参数传入Feign的请求方法中。这样,当请求返回时,就会自动调用处理类对返回值进行处理。 ```java // 调用Feign接口 FeignClient feignClient = new FeignClient(); ResponseProcessor<String> processor = new FeignResponseHandler<>(); feignClient.request(processor); // Feign接口定义 public interface FeignClient { @RequestLine("GET /api") void request(ResponseProcessor<String> processor); } ``` 这样,我们就可以将返回值处理逻辑统一封装到一个类中,并通过泛型适应不同的返回值类型。这样可以简化代码,并且使代码更具可维护性和可扩展性。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AE86Jag

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值