录制回放工具的开发过程中,有如下两个基本的场景:
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)
于是,还是指向了需要修改自动生成的代码的解决方案。
结果发现,出现了新的坑。原来 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反序列化的问题还需要继续研究。
感觉逐步在接近这个工具的能力边界了。