日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

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

發布時間:2023/12/3 编程问答 34 豆豆
生活随笔 收集整理的這篇文章主要介紹了 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對象序列化去掉字段

總結

以上是生活随笔為你收集整理的java对象序列化去掉字段_使用序列化查找对象中的脏字段的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。