使用序列化查找对象中的脏字段

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

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);}

上面的代码不能处理很多条件,例如空值,字段是集合,映射或数组等。但是,这给出了可以做什么的想法。 如果对象很小并且其中没有太多层次结构,则效果很好。 当在巨大的层次结构对象中的变化很小时,我们必须一直遍历最后一个对象以了解差异。 而且,使用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

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

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

相关文章

js操作table中tr的顺序,实现上移下移一行的效果

总体思路是在table外部加个div&#xff0c;修改div的innerHtml实现改变tr顺序的效果 具体思路是 获取当前要移动tr行的rowIndex&#xff0c;在table中删除掉&#xff0c;然后循环table的rows&#xff0c;到了目标行再直接加进去&#xff0c;最后把整体的html赋值给div完成效果…

oracle日记账单据编号未生成_商管财务数据平台Oracle与共享未付池差异如何核对、解决?...

‍‍近期&#xff0c;总部新上线财务数据平台啦&#xff01;各个系统间的差异异常数据清晰可见&#xff0c;随时可查&#xff0c;今天就和小伙伴们一起分享一下Oracle与共享未付池差异如何核对、解决。首先&#xff0c;将Oracle与共享未付池差异数据导出。由于导出的数据包括本…

python (六)函数

一、函数的形成 需求1&#xff1a;来测试一下‘hello word’ 的长度 # 在没有函数的时候&#xff0c;我们可以用for循环实现 s1 "hello world" length 0 for i in s1:length length1 print(length) 再增加一个需求2&#xff1a;再来测试一下另外一个字符串的长度&…

Java方法中的参数太多,第4部分:重载

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

android paint 圆角 绘制_[BOT] 一种android中实现“圆角矩形”的方法

内容简介文章介绍ImageView(方法也可以应用到其它View)圆角矩形(包括圆形)的一种实现方式&#xff0c;四个角可以分别指定为圆角。思路是利用“Xfermode Path”来进行Bitmap的裁剪。背景圆角矩形实现的方法应该很多&#xff0c;网上一大堆。很怀疑为啥安卓的控件不内置这样的属…

解决高度塌陷问题

所谓高度塌陷就是在文档流中&#xff0c;父元素的高度默认是被子元素撑开的&#xff0c;也就是子元素多高&#xff0c;父元素就多高。但是当为子元素设置浮动以后&#xff0c;子元素会完全脱离文档流&#xff0c;此时将会导致子元素无法撑起父元素的高度&#xff0c;导致父元素…

HDU2035 - 人见人爱A^B

求A^B的最后三位数表示的整数。 说明&#xff1a;A^B的含义是“A的B次方” Input 输入数据包含多个测试实例&#xff0c;每个实例占一行&#xff0c;由两个正整数A和B组成&#xff08;1<A,B<10000&#xff09;&#xff0c;如果A0, B0&#xff0c;则表示输入数据的结束&…

Cisco TrustSec(理解)

1、Cisco TrustSec的限制当指定了无效的设备ID时&#xff0c;受保护的访问凭据&#xff08;Protected access credential&#xff0c;PAC&#xff09;设置将失败并保持挂起状态。 即使在清除PAC并配置正确的设备ID和密码后&#xff0c;PAC仍然会失败。作为解决方法&#xff0c;…

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

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

web前端(12)—— 页面布局2

本篇博文&#xff0c;主要就讲定位的问题&#xff0c;也就是页面布局里最重要的&#xff0c;本篇博文不出意外的话&#xff0c;也是css的最后一篇博文了 定位&#xff0c;position属性 定位有三种&#xff1a; 相对定位绝对定位固定定位 相对定位&#xff0c;position&#x…

51Nod.1766.树上最远点对(树的直径 RMQ 线段树/ST表)

题目链接 \(Description\) 给定一棵树。每次询问给定\(a\sim b,c\sim d\)两个下标区间&#xff0c;从这两个区间中各取一个点&#xff0c;使得这两个点距离最远。输出最远距离。\(n,q\leq10^5\)。 \(Solution\) 一个集合直径的两端点&#xff0c;在被划分为两个集合后一定是两个…

Web应用程序中的Spring JDBC入门

在上一篇文章中&#xff0c;我已经向您展示了如何设置基本的Spring 3 MVC Web应用程序 。 重复使用该项目设置作为模板&#xff0c;我将向您展示如何增强它以与JDBC一起使用。 有了它&#xff0c;您可以存储和检索数据库中的数据。 我们将通过Spring添加一个新的控制器和一个数…

python pyplot中axis_Python Pyplot xaxis未显示在图形上

pyplot未在图形上显示x轴&#xff1a;import pandas as pdimport matplotlib.pyplot as pltdf pd.read_csv(sitka_weather_2014.csv)df[AKST] pd.to_datetime(df.AKST)df[Dates] df[AKST].dt.strftime(%b %d, %Y)df.set_index("Dates", inplace True)# Plot Dataf…

为什么dubbo的调用重试不建议设置成超过1

前面提到过&#xff0c;重试是靠ClusterInvoker来保证的&#xff0c;不同的Cluster在调用失败的时候 做不同处理 比如默认的FailoverClusterInvoke的doInvoke方法里面&#xff1a;int len getUrl().getMethodParameter(invocation.getMethodName(), Constants.RETRIES_KEY, Co…

web前端入门学习(纯干货)

web前端怎么样才能入门&#xff0c;首先我们要从什么是初级web前端工程师说起&#xff1a; 按照我的想法&#xff0c;我把前端工程师分为了入门、初级、中级、高级这四个级别&#xff0c; 入门级别指的是了解什么是前端&#xff08;前端到底是什么其实很多人还是不清楚的&…

用BlockingExecutor限制任务提交

JDK的java.util.concurrent.ThreadPoolExecutor允许您将任务提交到线程池&#xff0c;并使用BlockingQueue来保存提交的任务。 如果您要提交数千个任务&#xff0c;请指定一个“绑定”队列&#xff08;即最大容量的队列&#xff09;&#xff0c;否则JVM可能会耗尽内存。 您可以…

[校内模拟题2]

水题 但是原地螺旋炸裂 都不好意思贴代码了QWQ enc 【问题背景】 zhx 和他的妹子聊天。 【问题描述】 考虑一种简单的加密算法。假定所有句子都由小写英文字母构成&#xff0c; 对于每一个字母&#xff0c; 我们将它唯一地映射到另一个字母。 例如考虑映射规则&#xff1a;a-&g…

AJAX初识(原生JS版AJAX和Jquery版AJAX)

一、什么是JSON 1.介绍JSON独立于语言&#xff0c;是一种与语言无关的数据格式。JSON指的是JavaScript对象表示法&#xff08;JavaScript Object Notation&#xff09;JSON是轻量级的文本数据交换格式JSON具有自我描述性&#xff0c;更易理解JSON使用JavaScript语法来描述数据对…

python保存为xlsb_Read XLSB File in Pandas Python

问题There are many questions on this, but there has been no simple answer on how to read an xlsb file into pandas. Is there an easy way to do this?回答1:Hi actually there is a way. Just use pyxlsb library.import pandas as pdfrom pyxlsb import open_workboo…

内存不足而没有OutOfMemoryError

这实际上是最初发布于2010年的帖子的转世。 昨天&#xff0c;当听到我们的工程师咒骂一个特别令人讨厌的错误时&#xff0c;闪回发生了。 当诅咒停止时&#xff0c;我走过去核实我的怀疑。 瞧&#xff0c;我是正确的–情绪波动是由应用程序用尽了堆空间导致的&#xff0c;但死于…