java对象序列化去掉字段_使用序列化查找对象中的脏字段
java對(duì)象序列化去掉字段
 假設(shè)您正在開發(fā)一個(gè)將對(duì)象自動(dòng)保存到數(shù)據(jù)庫中的框架。 您需要檢測(cè)兩次保存之間所做的更改,以便僅保存已修改的字段。 如何檢測(cè)臟場(chǎng)。 最簡(jiǎn)單的方法是遍歷原始數(shù)據(jù)和當(dāng)前數(shù)據(jù),并分別比較每個(gè)字段。 代碼如下: 
上面的代碼不能處理很多條件,例如null值,字段是集合,映射或數(shù)組等。但是,這給出了可以做什么的想法。 如果對(duì)象很小并且其中不包含太多層次結(jié)構(gòu),則效果很好。 當(dāng)在巨大的層次結(jié)構(gòu)對(duì)象中的變化很小時(shí),我們必須一直遍歷到最后一個(gè)對(duì)象才能知道差異。 而且,使用equals可能不是檢測(cè)臟字段的正確方法。 可能尚未實(shí)現(xiàn)等于,或者僅可以僅比較幾個(gè)字段,所以沒有進(jìn)行真正的臟字段檢測(cè)。 您必須遍歷每個(gè)字段,而不論是否相等,直到您擊中圖元來檢測(cè)臟字段為止。
在這里,我想談?wù)剻z測(cè)臟場(chǎng)的另一種方法。 代替使用反射,我們可以使用序列化來檢測(cè)臟字段。 我們可以輕松地替換上面代碼中的“等于”來序列化對(duì)象,并且僅當(dāng)字節(jié)不同時(shí)才繼續(xù)操作。 但這不是最佳選擇,因?yàn)槲覀儗⒍啻涡蛄谢粚?duì)象。 我們需要如下邏輯:
- 序列化要比較的兩個(gè)對(duì)象
- 比較兩個(gè)字節(jié)流時(shí),檢測(cè)要比較的字段
- 如果字節(jié)值不同,則將該字段存儲(chǔ)為不同
- 收集所有不同的字段并返回
因此,一次遍歷兩個(gè)字節(jié)流可以生成不同字段的列表。 我們?nèi)绾螌?shí)現(xiàn)這種邏輯? 我們可以遍歷序列化流并能夠識(shí)別其中的字段嗎? 我們要編寫如下代碼:
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應(yīng)該包含_simple._string,以便我們可以直接轉(zhuǎn)到_string并對(duì)其進(jìn)行處理。
解釋序列化格式
有些文章解釋了標(biāo)準(zhǔn)序列化字節(jié)流的外觀 。 但是,我們將使用自定義格式。 雖然我們可以閱讀標(biāo)準(zhǔn)的序列化格式,但是當(dāng)類的結(jié)構(gòu)已經(jīng)由我們的類定義時(shí),它就不必要了。 我們將簡(jiǎn)化它,并更改序列化的格式以僅寫入字段的類型。 字段的類型是必需的,因?yàn)轭惵暶骺梢砸媒涌?#xff0c;超類等,而所包含的值可以是派生類型。
為了自定義序列化,我們創(chuàng)建了自己的ObjectOutputStream并覆蓋了writeClassDescriptor函數(shù)。 現(xiàn)在,我們的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);} }讓我們編寫一個(gè)簡(jiǎn)單的對(duì)象進(jìn)行序列化,并查看字節(jié)流的外觀:
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();} }運(yùn)行此類后,調(diào)用“ 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按照本文中的格式,我們可以將字節(jié)跟蹤為:
- AC ED:STREAM_MAGIC。 指定這是一個(gè)序列化協(xié)議。
- 00 05:STREAM_VERSION。 序列化版本。
- 0×73:TC_OBJECT。 指定這是一個(gè)新對(duì)象。
現(xiàn)在我們需要閱讀類描述符。
- 0×72:TC_CLASSDESC。 指定這是一個(gè)新類。
類描述符是我們編寫的,因此我們知道格式。 它已讀取兩個(gè)字符串。
- 0×74:TC_STRING。 指定對(duì)象的類型。
- 0×00 0×10:字符串的長(zhǎng)度,后跟對(duì)象類型的16個(gè)字符,即SimpleTestObject
- 0×74:TC_STRING。 指定類加載器
- 0×00 0×27:字符串的長(zhǎng)度,后跟類加載器名稱
- 0×78:TC_ENDBLOCKDATA,對(duì)象的可選塊數(shù)據(jù)的結(jié)尾。
- 0×70:TC_NULL,在結(jié)束塊之后,表示沒有超類
此后,將寫入類中不同字段的值。 我們的類_integer和_string中有兩個(gè)字段。 因此我們有4個(gè)字節(jié)的_integer值,即0×00、0×00、0×00、0x0A,后跟一個(gè)格式為字符串的字符串
- 0×74:TC_STRING
- 0×00 0×09:字符串的長(zhǎng)度
- 9個(gè)字節(jié)的字符串?dāng)?shù)據(jù)
比較流并檢測(cè)臟區(qū)
現(xiàn)在我們了解并簡(jiǎn)化了序列化格式,我們可以開始為流編寫解析器并對(duì)其進(jìn)行比較。 首先,我們?yōu)樵甲侄尉帉憳?biāo)準(zhǔn)的讀取函數(shù)。 例如,如下所示編寫getInt以讀取整數(shù)(示例代碼中存在其他整數(shù)):
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;}在這里,我們讀取第一個(gè)字節(jié),如果它是TC_CLASSDESC,則讀取兩個(gè)字符串。 然后,我們繼續(xù)閱讀,直到達(dá)到TC_NULL。 還有其他條件要處理,例如TC_REFERENCE,它是對(duì)先前聲明的值的引用。 可以在示例代碼中找到。
注意:函數(shù)同時(shí)讀取兩個(gè)字節(jié)流(_reading和_compareTo)。 因此,他們兩個(gè)總是指向下一步必須開始比較的地方。 字節(jié)被讀取為一個(gè)塊,這確保即使存在值差異,我們也將始終從正確的位置開始。 例如,字符串塊的長(zhǎng)度指示直到讀取的位置,類描述符的末尾指示直到讀取的位置,依此類推。
我們尚未編寫字段序列。 我們?nèi)绾沃酪喿x哪些字段? 為此,我們可以執(zhí)行以下操作:
Class cls = Class.forName(clsname, false, this.getClass().getClassLoader());ObjectStreamClass ostr = ObjectStreamClass.lookup(cls);ObjectStreamField[] flds = ostr.getFields();這為我們提供了序列化順序的字段。 如果我們遍歷flds,將按照寫入數(shù)據(jù)的順序進(jìn)行。 因此,我們可以如下迭代它:
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} }在這里,我僅說明了如何檢查類中的原始字段是否存在差異。 但是,可以通過遞歸調(diào)用對(duì)象字段類型的相同函數(shù),將邏輯擴(kuò)展到子類。
您可以在此處找到此博客要嘗試的示例代碼,該代碼具有比較子類和超類的邏輯。 在這里可以找到更整潔的實(shí)現(xiàn)。
請(qǐng)注意。 此方法存在一些缺點(diǎn):
- 此方法只能使用可序列化的對(duì)象和字段。 暫態(tài)和靜態(tài)字段之間沒有差異。
- 如果writeObject覆蓋默認(rèn)的序列化,則ObjectStreamClass將無法正確反映序列化的字段。 為此,我們將不得不對(duì)這些類的讀取進(jìn)行硬編碼。 例如,在示例代碼中,存在對(duì)ArrayList的讀取或使用并解析標(biāo)準(zhǔn)序列化格式。
翻譯自: https://www.javacodegeeks.com/2013/11/using-serialization-to-find-dirty-fields-in-an-object.html
java對(duì)象序列化去掉字段
總結(jié)
以上是生活随笔為你收集整理的java对象序列化去掉字段_使用序列化查找对象中的脏字段的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: 安卓云服务器怎么登陆(安卓云服务器)
- 下一篇: Java的未来项目:巴拿马,织布机,琥珀
