成都网站建设贴吧,医院网站建设医生需要做什么,百家号关键词排名,北京工装装修公司假设您正在开发一个将对象自动保存到数据库中的框架。 您需要检测两次保存之间所做的更改#xff0c;以便仅保存修改过的字段。 如何检测脏场。 最简单的方法是遍历原始数据和当前数据#xff0c;并分别比较每个字段。 代码如下#xff1a; public static void getDirtyFie… 假设您正在开发一个将对象自动保存到数据库中的框架。 您需要检测两次保存之间所做的更改以便仅保存修改过的字段。 如何检测脏场。 最简单的方法是遍历原始数据和当前数据并分别比较每个字段。 代码如下 public static void getDirtyFields(Object obj, Object obj2, Class cls, MapString, 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;}MapString, DiffFields fdiffs new HashMapString, 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 {bString name desc.forClass().getName();writeObject(name);/bString 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 |pClassLoader35c|
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 EDSTREAM_MAGIC。 指定这是一个序列化协议。 00 05STREAM_VERSION。 序列化版本。 0×73TC_OBJECT。 指定这是一个新对象。 现在我们需要阅读类描述符。 0×72TC_CLASSDESC。 指定这是一个新类。 类描述符是我们编写的因此我们知道格式。 它已读取两个字符串。 0×74TC_STRING。 指定对象的类型。 0×00 0×10字符串的长度后跟对象类型的16个字符即SimpleTestObject 0×74TC_STRING。 指定类加载器 0×00 0×27字符串的长度后跟类加载器名称 0×78TC_ENDBLOCKDATA对象的可选块数据的结尾。 0×70TC_NULL在结束块之后表示没有超类的事实 此后将写入类中不同字段的值。 我们的类_integer和_string中有两个字段。 因此我们有4个字节的_integer值即0×00、0×00、0×00、0x0A后跟一个格式为字符串的字符串 0×74TC_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