java对象序列化去掉字段_使用序列化查找对象中的脏字段
java對象序列化去掉字段
 假設您正在開發一個將對象自動保存到數據庫中的框架。 您需要檢測兩次保存之間所做的更改,以便僅保存已修改的字段。 如何檢測臟場。 最簡單的方法是遍歷原始數據和當前數據,并分別比較每個字段。 代碼如下: 
上面的代碼不能處理很多條件,例如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的讀取或使用并解析標準序列化格式。
 
翻譯自: https://www.javacodegeeks.com/2013/11/using-serialization-to-find-dirty-fields-in-an-object.html
java對象序列化去掉字段
總結
以上是生活随笔為你收集整理的java对象序列化去掉字段_使用序列化查找对象中的脏字段的全部內容,希望文章能夠幫你解決所遇到的問題。
                            
                        - 上一篇: 安卓云服务器怎么登陆(安卓云服务器)
 - 下一篇: Java的未来项目:巴拿马,织布机,琥珀