java对象序列化去掉字段_使用序列化查找对象中的脏字段

java对象序列化去掉字段

假设您正在开发一个将对象自动保存到数据库中的框架。 您需要检测两次保存之间所做的更改,以便仅保存已修改的字段。 如何检测脏场。 最简单的方法是遍历原始数据和当前数据,并分别比较每个字段。 代码如下:

public static void getDirtyFields(Object obj, Object obj2, Class cls, Map<String, DiffFields> diff)throws Exception {Field[] flds = cls.getDeclaredFields();for (int i = 0; i < flds.length; i++) {flds[i].setAccessible(true);Object fobj = flds[i].get(obj);Object fobj2 = flds[i].get(obj2);if (fobj.equals(fobj2)) continue;if (checkPrimitive(flds[i].getType())) {<!-- add to dirty fields -->continue;}Map<String, DiffFields> fdiffs = new HashMap<String, DiffFields>();getDirtyFields(fobj, fobj2, fobj.getClass(), fdiffs);<!-- add to dirty fields -->}if (cls.getSuperclass() != null)getDirtyFields(obj, obj2, cls.getSuperclass(), diff);}

上面的代码不能处理很多条件,例如null值,字段是集合,映射或数组等。但是,这给出了可以做什么的想法。 如果对象很小并且其中不包含太多层次结构,则效果很好。 当在巨大的层次结构对象中的变化很小时,我们必须一直遍历到最后一个对象才能知道差异。 而且,使用equals可能不是检测脏字段的正确方法。 可能尚未实现等于,或者仅可以仅比较几个字段,所以没有进行真正的脏字段检测。 您必须遍历每个字段,而不论是否相等,直到您击中图元来检测脏字段为止。

在这里,我想谈谈检测脏场的另一种方法。 代替使用反射,我们可以使用序列化来检测脏字段。 我们可以轻松地替换上面代码中的“等于”来序列化对象,并且仅当字节不同时才继续操作。 但这不是最佳选择,因为我们将多次序列化同一对象。 我们需要如下逻辑:

  • 序列化要比较的两个对象
  • 比较两个字节流时,检测要比较的字段
  • 如果字节值不同,则将该字段存储为不同
  • 收集所有不同的字段并返回

因此,一次遍历两个字节流可以生成不同字段的列表。 我们如何实现这种逻辑? 我们可以遍历序列化流并能够识别其中的字段吗? 我们要编写如下代码:

public static void main(String[] args) throws Exception {ComplexTestObject obj = new ComplexTestObject();ComplexTestObject obj2 = new ComplexTestObject();obj2._simple._string = "changed";//serialize the first object and get the bytesByteArrayOutputStream ostr = new ByteArrayOutputStream();CustomOutputStream str = new CustomOutputStream(ostr);str.writeObject(obj);str.close();byte[] bytes = ostr.toByteArray();//serialize the second object and get the bytesostr = new ByteArrayOutputStream();str = new CustomOutputStream(ostr);str.writeObject(obj2);str.close();byte[] bytes1 = ostr.toByteArray();       //read and compare the bytes and get back a list of differing fieldsReadSerializedStream check = new ReadSerializedStream(bytes, bytes1);Map diff = check.compare();System.out.println("Got difference: " + diff);}

Map应该包含_simple._string,以便我们可以直接转到_string并对其进行处理。

解释序列化格式

有些文章解释了标准序列化字节流的外观 。 但是,我们将使用自定义格式。 虽然我们可以阅读标准的序列化格式,但是当类的结构已经由我们的类定义时,它就不必要了。 我们将简化它,并更改序列化的格式以仅写入字段的类型。 字段的类型是必需的,因为类声明可以引用接口,超类等,而所包含的值可以是派生类型。

为了自定义序列化,我们创建了自己的ObjectOutputStream并覆盖了writeClassDescriptor函数。 现在,我们的ObjectOutputStream如下所示:

public class CustomOutputStream extends ObjectOutputStream {public CustomOutputStream(OutputStream str)throws IOException  {super(str);}@Overrideprotected void writeClassDescriptor(ObjectStreamClass desc)throws IOException  {<b>String name = desc.forClass().getName();writeObject(name);</b>String ldr = "system";ClassLoader l = desc.forClass().getClassLoader();if (l != null)  ldr = l.toString();if (ldr == null)  ldr = "system";writeObject(ldr);}
}

让我们编写一个简单的对象进行序列化,并查看字节流的外观:

public class SimpleTestObject implements java.io.Serializable {int _integer;String _string;public SimpleTestObject(int b)  {_integer = 10;_string = "TestData" + b;}public static void main(String[] args) throws Exception  {SimpleTestObject obj = new SimpleTestObject(0);FileOutputStream ostr = new FileOutputStream("simple.txt");CustomOutputStream str = new CustomOutputStream(ostr);str.writeObject(obj);str.close(); ostr.close();}
}

运行此类后,调用“ hexdump -C simple.txt”,显示以下输出:

00000000  ac ed 00 05 73 72 74 00  10 53 69 6d 70 6c 65 54  |....srt..SimpleT|
00000010  65 73 74 4f 62 6a 65 63   74 74 00 27 73 75 6e 2e  |estObjectt.'sun.|
00000020  6d 69 73 63 2e 4c 61 75  6e 63 68 65 72 24 41 70  |misc.Launcher$Ap|
00000030  70 43 6c 61 73 73 4c 6f   61 64 65 72 40 33 35 63  |pClassLoader@35c|
00000040  65 33 36 78 70 00 00 00  0a 74 00 09 54 65 73 74  |e36xp....t..Test|
00000050  44 61 74 61 30                                                          |Data0|
00000055

按照本文中的格式,我们可以将字节跟踪为:

  • AC ED:STREAM_MAGIC。 指定这是一个序列化协议。
  • 00 05:STREAM_VERSION。 序列化版本。
  • 0×73:TC_OBJECT。 指定这是一个新对象。

现在我们需要阅读类描述符。

  • 0×72:TC_CLASSDESC。 指定这是一个新类。

类描述符是我们编写的,因此我们知道格式。 它已读取两个字符串。

  • 0×74:TC_STRING。 指定对象的类型。
  • 0×00 0×10:字符串的长度,后跟对象类型的16个字符,即SimpleTestObject
  • 0×74:TC_STRING。 指定类加载器
  • 0×00 0×27:字符串的长度,后跟类加载器名称
  • 0×78:TC_ENDBLOCKDATA,对象的可选块数据的结尾。
  • 0×70:TC_NULL,在结束块之后,表示没有超类

此后,将写入类中不同字段的值。 我们的类_integer和_string中有两个字段。 因此我们有4个字节的_integer值,即0×00、0×00、0×00、0x0A,后跟一个格式为字符串的字符串

  • 0×74:TC_STRING
  • 0×00 0×09:字符串的长度
  • 9个字节的字符串数据

比较流并检测脏区

现在我们了解并简化了序列化格式,我们可以开始为流编写解析器并对其进行比较。 首先,我们为原始字段编写标准的读取函数。 例如,如下所示编写getInt以读取整数(示例代码中存在其他整数):

static int getInt(byte[] b, int off) {return ((b[off + 3] & 0xFF) << 0) +  ((b[off + 2] & 0xFF) << 8) +((b[off + 1] & 0xFF) << 16) + ((b[off + 0]) << 24);}

可以使用以下代码读取类描述符。

byte desc = _reading[_readIndex++]; //read TC_CLASSDESCbyte cdesc = _compareTo[_compareIndex++];switch (desc) {case TC_CLASSDESC: {byte what = _reading[_readIndex++];  byte cwhat = _compareTo[_compareIndex++]; //read the type written TC_STRINGif (what == TC_STRING) {String[] clsname = readString(); //read the field Type if (_reading[_readIndex] == TC_STRING) {what = _reading[_readIndex++];  cwhat = _compareTo[_compareIndex++];String[] ldrname = readString(); //read the classloader name}ret.add(clsname[0]);cret.add(clsname[1]);}byte end = _reading[_readIndex++]; byte cend = _compareTo[_compareIndex++]; //read 0x78 TC_ENDBLOCKDATA//we read again so that if there are super classes, their descriptors are also read//if we hit a TC_NULL, then the descriptor is readreadOneClassDesc(); }break;case TC_NULL://ignore all subsequent nulls while (_reading[_readIndex] == TC_NULL) desc = _reading[_readIndex++];while (_compareTo[_compareIndex] == TC_NULL) cdesc = _compareTo[_compareIndex++];break;}

在这里,我们读取第一个字节,如果它是TC_CLASSDESC,则读取两个字符串。 然后,我们继续阅读,直到达到TC_NULL。 还有其他条件要处理,例如TC_REFERENCE,它是对先前声明的值的引用。 可以在示例代码中找到。

注意:函数同时读取两个字节流(_reading和_compareTo)。 因此,他们两个总是指向下一步必须开始比较的地方。 字节被读取为一个块,这确保即使存在值差异,我们也将始终从正确的位置开始。 例如,字符串块的长度指示直到读取的位置,类描述符的末尾指示直到读取的位置,依此类推。

我们尚未编写字段序列。 我们如何知道要阅读哪些字段? 为此,我们可以执行以下操作:

Class cls = Class.forName(clsname, false, this.getClass().getClassLoader());ObjectStreamClass ostr = ObjectStreamClass.lookup(cls);ObjectStreamField[] flds = ostr.getFields();

这为我们提供了序列化顺序的字段。 如果我们遍历flds,将按照写入数据的顺序进行。 因此,我们可以如下迭代它:

Map diffs = new HashMap();
for (int i = 0; i < flds.length; i++) {DiffFields dfld = new DiffFields(flds[i].getName());if (flds[i].isPrimitive()) { //read primitivesObject[] read = readPrimitive(flds[i]);if (!read[0].equals(read[1])) diffs.put(flds[i].getName(), dfld); //Value is not the same so add as different}else if (flds[i].getType().equals(String.class)) { //read stringsbyte nxtread = _reading[_readIndex++]; byte nxtcompare = _compareTo[_compareIndex++];String[] rstr = readString();if (!rstr[0].equals(rstr[1])) diffs.put(flds[i].getName(), dfld); //String not same so add as difference}
}

在这里,我仅说明了如何检查类中的原始字段是否存在差异。 但是,可以通过递归调用对象字段类型的相同函数,将逻辑扩展到子类。

您可以在此处找到此博客要尝试的示例代码,该代码具有比较子类和超类的逻辑。 在这里可以找到更整洁的实现。

请注意。 此方法存在一些缺点:

  • 此方法只能使用可序列化的对象和字段。 暂态和静态字段之间没有差异。
  • 如果writeObject覆盖默认的序列化,则ObjectStreamClass将无法正确反映序列化的字段。 为此,我们将不得不对这些类的读取进行硬编码。 例如,在示例代码中,存在对ArrayList的读取或使用并解析标准序列化格式。

参考: 使用序列化从JCG合作伙伴 Raji Sankar在Reflections博客上找到对象中的脏区 。

翻译自: https://www.javacodegeeks.com/2013/11/using-serialization-to-find-dirty-fields-in-an-object.html

java对象序列化去掉字段

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/346232.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

c语言中怎么定义的字符串,C语言中定义字符串的几种方式

1&#xff0c;什么是字符串&#xff1f;所谓字符串本质上就是以\0作为结尾的特殊字符数组&#xff1b;2&#xff0c;定义字符串的过程中有哪些注意点由于字符串本质上其实就是以\0作为结尾的特殊字符数组&#xff0c;所以定义字符串时&#xff0c;必须保证字符串存储的最后一个…

ios 不被遮挡 阴影_解决ios10导航栏底部阴影线条隐藏失效问题

2016.11.13 21:47* 字数 887 阅读 791评论 0喜欢 16一、置空阴影图片基于iOS9,在iOS10上不好使.[self.navigationController.navigationBar setShadowImage:[UIImage new]];二、剪裁navigationBar首先看看UIView的clipsToubounds属性在SDK中的描述&#xff1a;property(nonatom…

Java的未来项目:巴拿马,织布机,琥珀和瓦尔哈拉

新闻稿“ Oracle Code One Java主题演讲概述了Java的未来 ”在Oracle Code“突出未来项目” Valvala 项目&#xff0c;巴拿马 项目&#xff0c;Amber 项目和Loom 项目中描述了Java主旨。 这篇文章为不熟悉上述项目的人提供了每个项目的简要摘要以及与每个项目相关的一些最新工作…

c语言选择题答案在哪查,C语言选择题及答案

C语言选择题及答案成绩的取得离不开我们平时的努力&#xff0c;以下是百分网小编为大家整理的C语言选择题及答案&#xff0c;希望对大家的学习起到帮助!选择题1.在深度为5的满二叉树叶中&#xff0c;叶子结点的个数为( )。A.32B.31C.16D.152.软件生命周期中&#xff0c;花费最多…

vs2019中如何创建qt项目_在VS2015中创建Qt项目【VS+Qt项目开发系列】(二)

在VS2015中创建Qt项目【VSQt项目开发系列】(二)发布时间&#xff1a;2018-04-20 22:44,浏览次数&#xff1a;1269, 标签&#xff1a;VSQt在上一篇【VSQt项目开发】(一)在VS2015中安装Qt环境中&#xff0c;我们完成了开发环境的安装&#xff0c;现在我们正式创建我们的项目。项目…

c语言ok未定义标识符,C语言中宏的相关知识 - osc_y7ckpzr9的个人空间 - OSCHINA - 中文开源技术交流社区...

2019/04/27 16:02 1.宏的定义&#xff1a;宏定义就是预处理命令的一种&#xff0c;它允许用一个标识符来表示一个字符串。格式如下&#xff1a;#define name(宏名) stuff(字符串)本质就是使用宏名去替代字符串的内容&#xff0c;注意是原封不动的替换&#xff0c;不要自己潜意识…

pythoni屏幕连点_【Flutter组件】仿抖音双击点赞弹出爱心效果(可连点)

效果简介仿抖音点赞手势&#xff0c;单击暂停&#xff0c;双击点赞&#xff0c;可连续点击添加多个爱心&#xff0c;特点如下全部效果为代码绘制(爱心图标来自Material Icon的图标)套上在目标Widget外即可使用提供单击与点赞的回调建议复制代码使用&#xff0c;动画可按需修改没…

java8 linq4j_Java 8仍然需要LINQ吗? 还是比LINQ更好?

java8 linq4j长期以来&#xff0c; LINQ是.NET软件工程生态系统中发生的最好的事情之一。 通过在Visual Studio 2008中引入lambda表达式和monad &#xff0c;它使C&#xff03;语言跃居Java之前&#xff0c;Java在当时是版本6&#xff0c;但仍在讨论泛型类型擦除的优缺点。 这项…

printf sizeof c语言,你可能对 sizeof(i++) 有点误解。。。

原标题&#xff1a;你可能对 sizeof(i) 有点误解。。。各位&#xff0c;今天还是按照惯例给大家分享一个C语言容易出现的小错误&#xff0c;这也是跟sizeof有关的&#xff0c;问题虽小&#xff0c;却可管中窥豹&#xff0c;话不多说&#xff0c;代码先行&#xff1a;# includei…

mongodb上一篇下一篇_如何使用Microsoft office word—上一篇

从这一期开始&#xff0c;将会逐一介绍Microsoft office的几个常用的软件&#xff0c;例如word ppt excel等&#xff0c;每一个软件将分为几期介绍&#xff0c;这几期介绍word。注意&#xff1a;介绍软件版本是Microsoft office365。如何插入图片首先打开word&#xff0c;点击插…

Java:使用Mockito模拟ResultSet

这篇文章展示了如何使用Mockito模拟java.sql.ResultSet 。 它可用于帮助单元测试代码对ResultSet &#xff08;例如ResultSetExtractor &#xff09;执行操作&#xff0c;而无需依赖外部数据源。 您可以通过提供列名列表和2D数据数组来创建MockResultSet 。 例如&#xff1a; …

c语言如何随机获取1kb,基于VS2010+C语言实现播放器的顺序播放、随机播放

1.[文件] music.h ~ 920B 下载(38)/** File: music.h* Time: 2014/10/11*/#ifndef __MUSIC_H__#define __MUSIC_H__typedef enum { UNPLAYED, PLAYED } BOOL; // 自定义一个bool类型typedef enum { ORDER, RANDOM } PLAY_MODEL; // 自定义一个播放类型typedef char *MUSIC_…

rmi远程代码执行漏洞_fastjson远程代码执行漏洞复现

漏洞原理fastjson提供了autotype功能&#xff0c;在请求过程中&#xff0c;我们可以在请求包中通过修改type的值&#xff0c;来反序列化为指定的类型&#xff0c;而fastjson在反序列化过程中会设置和获取类中的属性&#xff0c;如果类中存在恶意方法&#xff0c;就会导致代码执…

c语言编简单博弈小游戏,[2018年最新整理]实验二:利用α-β搜索过程的博弈树搜索算法编写一字棋游戏.doc...

[2018年最新整理]实验二&#xff1a;利用α-β搜索过程的博弈树搜索算法编写一字棋游戏实验二&#xff1a;利用α-β搜索过程的博弈树搜索算法编写一字棋游戏一、实验目的与要求(1)了解极大极小算法的原理和使用方法&#xff0c;并学会用α-β剪枝来提高算法的效率。(2)使用C语…

java中重载 参数顺序_Java方法中的参数太多,第4部分:重载

java中重载 参数顺序期望将过多的参数传递给Java方法的问题之一是&#xff0c;该方法的客户端很难确定它们是否以适当的顺序传递了适当的值。 在以前的文章中&#xff0c;我描述了如何使用自定义类型 &#xff0c; 参数对象和构建器来解决此问题。 解决此问题的另一种方法&…

c语言金箍棒答案,《西游记》阅读测试题(带答案)

《西游记》测试题(后附答案)姓名得分1.《如来佛辨识假猴王》故事中,那只假孙悟空是什么猴子变的&#xff1f;()A&#xff0e;金丝猴 B&#xff0e;石猴 C.六耳猕猴2.金角大王和银角大王原来是()的弟子。A&#xff0e;太上老君 B&#xff0e;孙悟空 C&#xff0e;观音菩萨3.蜘蛛…

五个金念什么_四个“金”字读什么?

展开全部读音为&#xff1a;jīn &#xff0c;是金字的异体字写法&#xff0c;读音和意思与金字完全相同&#xff0c;打不62616964757a686964616fe58685e5aeb931333431353265出来&#xff0c;如下图&#xff1a;金拼音&#xff1a;jīn释义&#xff1a;1.俗称金子。金属元素&am…

Istio的零停机滚动更新

本系列文章的第一部分介绍了如何在Kubernetes集群中实现真正的零停机时间更新。 我们专门解决了将流量从旧实例切换到新实例时出现的请求失败。 本文将展示如何使用Istio群集实现相同的目标。 服务网格技术&#xff08;例如Istio&#xff09;通常与容器编排结合使用。 Istio以…

聚类dbi指数_一种基于DBI-PD聚类算法的异常检测机制

一种基于DBI-PD聚类算法的异常检测机制丁姝郁【期刊名称】《电脑开发与应用》【年(卷),期】2015(000)002【摘要】分析了网络数据维数和检测准确度之间的关系&#xff0c;介绍了常用于入侵检测的聚类分析方法及其优缺点。在此基础上&#xff0c;提出一种以戴维森堡丁指数(DBI)为…

单片机红外通信c语言,基于C语言的计算机与多单片机红外无线串口通信的实现.doc...

基于C语言的计算机与多单片机红外无线串口通信的实现基于C语言的计算机与多单片机红外无线串口通信的实现黄文亮 信息学院 电子信息工程专业指导教师 刘传菊摘要&#xff1a;计算机与一台或多台单片机的通信系统中的数据通讯一般采用的是串行通信方式。串行通信可采用有线与无线…