前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >切面中如何实现泛型返回值的反序列化

切面中如何实现泛型返回值的反序列化

作者头像
Antony
发布2021-06-28 10:41:37
2.7K0
发布2021-06-28 10:41:37
举报

问题:

泛型方法的返回值类型被擦除,导致录制的数据无法被正确反序列化。

产生场景:

在Http服务的远程调用中,HTTP请求的响应是这样定义的 @Data class ResponseResult { String errorCode; String errorMessage; Object data; } 通过这个方式,将服务端响应进行了统一。如果服务端处理请求出现问题,将通过errorCode进行告知。如果errorCode=0,则服务端处理正确,客户端可以根据约定的类型,从data中获取到服务端返回的数据。

在服务间调用时,也是这样的数据结构。为了能够使用一个统一的RemoteService方法,一般会有如下的处理。

代码语言:javascript
复制
public class RemoteService {

    public <T> T doPost(String url, String body, TypeToken<T> dataType) {
        Type type=dataType.getType();
        MethodReturnTypeTest.ResponseResult responseResult=doPost(url,body,type);
        T resultData = (T)responseResult.getData();
        return resultData;
    }
}

这样做的好处是,通过传入dataType,来告知RemoteService的doPost方法如何将对端传回的数据转换成调用方所需要的数据实体。

那么问题就来了。在切面中,一般通过以下方式就可以获取到方法的返回类型type

代码语言:javascript
复制
@Around("xxx")    public Object doAround(ProceedingJoinPoint joinPoint) {

        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        Type type=method.getGenericReturnType();
        //...
    }

在录制回放的自动化测试场景中,如果请求匹配成功,可以将对应的依赖mock数据从文件或者某个url处获取到,然后用

代码语言:javascript
复制
gson.fromJson(str,type)

的方式实现反序列化。 如果切点是之前的doPost方法,那么恭喜你,这时候type得到的是“T”,也就是泛型。然后反序列化时,由于不知道需要转换的目标类型,直接就报错了。

从proceed处获取

由于存在泛型时,无法从切点方法的返回类型中获取到真正的返回结果,于是考虑从方法执行的结果中获取。例如切面方法中有如下的两行代码,

代码语言:javascript
复制
       Object obj=joinPoint.proceed();
       Class clazz=obj.getClass();
       String returnType=clazz.getTypeName(); 
      //record.setReturnType(returnType);

首先执行被切的方法,然后根据返回值来获取其Class。 由于Class类实现了Type接口,因此可以根据获得的返回值对象来获取其真正的Class,进而获取到类型。

代码语言:javascript
复制
public final class Class<T> implements Serializable, 
GenericDeclaration, Type, AnnotatedElement 

通过这个操作,类似<T>doPost这样的泛型方法的返回值就暴露出来了。 稍微修改一下反序列化方法,就可以实现录制文件的回放了。

代码语言:javascript
复制
String returnTypeName=record.getReturnType();  //获取类型
Class clazz=Class.forName(returnTypeName)
gson.fromJson(str,(Type)clazz)

遭遇到List

在完成了上述操作后,笔者经过测试,可以实现绝大部分场景下的录制回放了,包括针对泛型方法的录制。但是还是在某个场景下翻车了。 通过Debug发现,当时通过方法执行后获取到的返回值类型是 "java.util.List<com.palyground.MethodReturnTypeTest$Bean>" 而在这种情况下, 直接进行Class.forName() 会抛出ClassNotFoundException,也就是找不到这个类。直接通过反射,只能生成泛型的List实例,而不能直接指定List中的元素的类型。这样就造成了另外一个非常常见的问题,

代码语言:javascript
复制
List<Bean> list = gson.fromJson(str,List.class)

gson在处理返回时,会将list当中的内容当做Object类型来处理,也就是无法实现类型的转换了。

因此,问题就变成了如何实现类似以下的效果

代码语言:javascript
复制
List<Bean> beans= new ArrayList<Bean>;

而目前所知的信息就是类似这样的信息"java.util.List<Bean>" 。 于是,想到了如下的思路 1)根据获取到的"java.util.List<Bean>" ,从中提取出"Bean"

  1. 通过反射获取到Bean的类,并获取到Bean[].class
  2. 用Bean[]方式反序列化数据 4)将Bean[]转换成List<Bean>

齐活。

有如下的案例

代码语言:javascript
复制
public class MethodReturnTypeTest {
    Gson gson = new Gson();
    List<Bean> beans = new ArrayList<Bean>();
    @BeforeEach
    public void setBean(){
        Bean bean = new Bean();
        bean.setMethodName("name");
        bean.setReturnType("list");
        bean.setNumber(0);
        bean.setPrice(9.98);
        beans.clear();
        beans.add(bean);
    }

    @Test
    public void testFromJsonList() throws ClassNotFoundException {
        String beanString="java.util.List<com.palyground.MethodReturnTypeTest$Bean>";
        String dataReadFromFile=gson.toJson(beans);
       String elementName=beanString.substring(beanString.indexOf("<")+1,beanString.indexOf(">"));
                Class<?> clazz= Class.forName(elementName);
                Class arrayClass= Array.newInstance(clazz,1).getClass();
        List<Bean> beansList1= Arrays.asList(gson.fromJson(dataReadFromFile,(Type)arrayClass));
        System.out.println(gson.toJson(beansList1));
        assertThatJson(beans).isEqualTo(beansList1);
    }

参考:

Java泛型类型擦除以及类型擦除带来的问题 - 蜗牛大师 - 博客园 (cnblogs.com)

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-06-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 软件测试那些事 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 问题:
  • 产生场景:
    • 从proceed处获取
      • 遭遇到List
      相关产品与服务
      文件存储
      文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档