专栏首页软件测试那些事对象拷贝和序列化,题目越短,坑越大

对象拷贝和序列化,题目越短,坑越大

录制回放工具的开发过程中,有如下两个基本的场景:

1 如何深拷贝一个数组

在切点中,某个方法的入参是一个Object[] 的数组。考虑到入参在方法的执行前后是会有变化的,因此需要深拷贝数组以隔绝这种可能的变化。

2 如何序列化/反序列化一个对象

ServiceMock录制回放的基本设计思路是将方法的某次执行的要素序列化后保存到文件以实现录制。然后在用例执行时,通过还原来获取回放所需要的数据。

这个过程,实际上就是序列化/反序列化一个对象。在具体实现上,选择了当前比较流行的Json格式,以实现工程项目中更为复杂的需求。

在考虑第一个问题时,可以通过将数组中的记录逐个拷贝到新的Object[]中以实现深拷贝。不过还是有两个问题需要考虑

1 如果如果 Object[]数组中的某个记录是一个类的对象,那就得深拷贝这个类,而不是简单的复制

2 如果这个记录本身就是一个数据集合,如是一个Map对象,那就不能简单地进行复制Map本身,而需要进一步处理Map中的各个数据。

而在对象的深拷贝上,常见的方法是让对象去实现Object.clone的方法,从而实现深拷贝。而在这个场景中,作为一个测试工具,很难去要求开发人员为此而修改业务代码。

于是,考虑通过序列化/反序列化一个对象的方式来实现对象的深拷贝。这样,问题1也就转化成了问题2。

在最近的一个项目接入过程中,笔者又遇到一个奇葩的案例。一个团队使用了MyBatis提供的Generator来实现mapper代码的自动生成。

而在逆向工程中,为每个表对应的mapper类生成了一个查询专用的XxxExample类,以提供类似这样的方法

ListselectByExample(XxxExample example)

在拦截到selectByExample方法的执行,并记录到example入参时,使用GSON/FastJSON等工具进行序列化和反序列化会失败。

(图来自网络)

原因是XxxExample 中包含一个内部类Criteria,以用于定义SQL 语句where后的查询条件。这个类中持有一个私有的XxxExample example对象,可以通过

Protected Criteria(XxxExample example)的构造方法来传入example对象。而这个设计则导致了循环依赖,因此在序列化时导致了堆栈溢出。

也就是说,通过GSON.toJson 和fromJson的方式在这个情况下行不通了。

在这个情况下,可以考虑在Criteria类持有的私有example成员变量上增加@Transient上的注解,用以标记该属性不需要进行序列化,进而打断这个死循环。不过这就涉及到修改代码的问题了。

有没有不修改自动生成的Mapper 代码,让工具自行解决这个问题的方案呢?

通过探索,发现如果使用JACKSON,则可以自动侦测是否存在循环依赖,并实现序列化。这样,就达成了一半的目标。

接着就是进行反序列化了。

此时,新的问题又出现了。在反序列化时,又爆出了以下的错误,

No Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator.

排查了一下,原来是因为Criteria以及其基类GeneratedCriteria所提供的构造方法都是Protected类型的,没有public的构造方法。

Protected Criteria(XxxExample example)

于是,还是指向了需要修改自动生成的代码的解决方案。

挣扎当中,想到了用Java原生的ObjectInputStream和 ObjectOutputStream来实施序列化和反序列化。看看是不是可以绕过JSON的这些坑。

结果发现,出现了新的坑。原来 Criteria类并没有实现Serializable 这个接口,然后就抛NotSerializableException的异常了。原因是这样的,

因此,这条路也被挡住了。解决方案也是去修改自动生成的源码,在类上加上实现Serializable接口的申明。

这样,综合上述候选方案的探索,均指向了需要修改Mybatis的Generator生成代码的方式。

通过一番搜索,发现了以下的一个Plugin可以实现上述需求。mybatis-generator 插件扩展,序列化针对 实体类(model)、 example类

https://www.cxybb.com/article/weixin_34418883/92042218

作者Alex

核心是以下的一个方法,

 protected void makeSerializable(TopLevelClass topLevelClass, 
                                    IntrospectedTable introspectedTable) { 
        if (addGWTInterface) { 
            topLevelClass.addImportedType(gwtSerializable); 
            topLevelClass.addSuperInterface(gwtSerializable); 
        } 
 
        if (!suppressJavaInterface) { 
            topLevelClass.addImportedType(serializable); 
            topLevelClass.addSuperInterface(serializable); 
 
            Field field = new Field(); 
            field.setFinal(true); 
            field.setInitializationString("1L"); //$NON-NLS-1$ 
            field.setName("serialVersionUID"); //$NON-NLS-1$ 
            field.setStatic(true); 
            field.setType(new FullyQualifiedJavaType("long")); //$NON-NLS-1$ 
            field.setVisibility(JavaVisibility.PRIVATE); 
            context.getCommentGenerator().addFieldComment(field, introspectedTable); 
 
            topLevelClass.addField(field); 
        } 
    }

通过使用这个插件,在各个sample类中实现了Serialable接口。通过修改MyBatis Generator生成的Mapper代码,让Criteria类可序列化,就可以使用ObjectOutputStream和ObjectInputStream来实现对象的序列化和反序列化了。

这样,至少能实现需求1中关于如何深拷贝一个对象的问题了。至于问题2,使用JSON来序列化和反序列化这个对象。通过JACKSON可以实现一半,另外JSON反序列化的问题还需要继续研究。

感觉逐步在接近这个工具的能力边界了。

文章分享自微信公众号:
软件测试那些事

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

作者:风月同天测试人
原始发表时间:2021-12-25
如有侵权,请联系 cloudcommunity@tencent.com 删除。
登录 后参与评论
0 条评论

相关文章

  • PHP开发过程的那些坑(一) ——对象拷贝

    PHP开发过程的那些坑(一)——对象拷贝 (原创内容,转载请注明来源,谢谢) 坑: 做单元测试的过程中,想要运用@dataProvider方式分别传两个不同的对...

    用户1327360
  • 你不知道的 JSON.stringify() 的威力

    其实有很多有用的东西,当时学习了,也记住了,但是时间久了就是记不住,所以导致在日常开发中总是想不起来原来这个东西可以这么用,而去选择了更加复杂和麻烦的方式。其实...

    Nealyang
  • 如何处理dubbo反序列化失败之后留下的坑,点开看看

    写在前面:2020年面试必备的Java后端进阶面试题总结了一份复习指南在Github上,内容详细,图文并茂,有需要学习的朋友可以Star一下! GitHub地...

    用户5546570
  • JSON.stringify()

    对于undefined、任意的函数以及symbol三个特殊的值分别作为对象属性的值、数组元素、单独的值时JSON.stringify()将返回不同的结果。

    Karl Du
  • 每天一道剑指offer-序列化二叉树

    怎么序列化的,就怎么反序列化。这里 deserialize反序列化时对于序列化到 String[]arr的哪个结点值来了的变量 index有两个坑(都是笔者亲自...

    乔戈里
  • js对象的直接赋值、浅拷贝与深拷贝

    最近Vue项目中写到一个业务,就是需要把对话框的表单中的数据,每次点击提交之后,就存进一个el-table表格中,待多次需要的表单数据都提交进表格之后,再将这个...

    OwenZhang
  • Java的deepcopy

    我们封装了对DB查询的缓存,对于一个查询请求来说, 首先从redis里读取,如果命中缓存,则直接返回结果. 如果未命中缓存,从db中查询数据,返回结果,同时异步...

    呼延十
  • 前端面试(8)拷贝

    js 的基本数据类型的赋值,就是值传递。引用类型对象的赋值是将对象地址的引用赋值。这时候修改对象中的属性或者值,会导致所有引用这个对象的值改变。如果想要真的复制...

    leader755
  • 【“别跟我不会”系列】Java设计模式之原型模式

    原型模式是用于创建重复对象,同时保证性能,一般应用场景是我们需要重复创建多个对象,例如在循环体中赋值一个对象。原型模式是用于创建重复对象,同时保证性能,一般应用...

    23号杂货铺
  • JS的数据类型/判断方法/栈与堆/深浅拷贝

    用来检测:undefined、string、number、boolean、symbol、object、function 无法检测引用类型里的Array

    杨肆月
  • EDU-CTF TripleSigma题解

    EDU-CTF是台大、交大、台科大三个学校的校赛,题目感觉都不错。TripleSigma这道题的反序列化POP链很有意思,官方wp写的很简单,在这里分析一下。 ...

    安恒网络空间安全讲武堂
  • 3分钟快速阅读-《Effective Java》(七)

    cwl_java
  • Dimple在左耳听风ARTS打卡(第九期)

    所谓ARTS: 每周至少做一个LeetCode的算法题;阅读并点评至少一篇英文技术文章;学习至少一个技术技巧;分享一篇有观点和思考的技术文章。(也就是Algor...

    程序员小跃
  • Matlab自动化控制-Adrc自抗扰控制参数调节

    以最简单的线性组合方法(1)为例,大概有如下参数需要调节: TD: delta h ESO: B01、B02、B03和观测器带宽w0 非线性反馈:(beta1、...

    用户9925864
  • 爆肝 50 道 Python 面试题 (上)

    上面代码中a is b的结果是True但c is d的结果是False,这一点的确让人费解。CPython解释器出于性能优化的考虑,把频繁使用的整数对象用一个叫...

    数据STUDIO
  • 聊聊HashSet源码

    今天聊一下HashSet源码,HashSet内部基本使用HashMap来实现,本博客将通过一下几个方向讲解。

    haifeiWu
  • 关于领域模型转换的那些事儿

    我们在软件开发设计及开发过程中,习惯将软件横向拆分为几个层。比如常见的三层架构:表现层(VIEW/UI)、业务逻辑层(SERVICE/BAL)、数据访问层(DA...

    端碗吹水
  • 架构设计基础知识整理

    From http://msdn.microsoft.com/en-us/library/ff647859.aspx

    哲洛不闹
  • 【手册详解】Java序列化引发的血案

    【强制】当序列化类新增属性时,请不要修改 serialVersionUID 字段,以避免反序列失败;如果完全不兼容升级,避免反序列化混乱,那么请修改 seria...

    chenchenchen

扫码关注腾讯云开发者

领取腾讯云代金券