我在接受採訪時被問到了這個問題。面試官想知道如何讓一個對象不可變。然後他問我是否將這個對象串起來 - 它會破壞不變性嗎?如果是的話,我該如何預防呢?任何人都可以幫我理解這一點嗎?序列化/反序列化如何破壞不變性?
回答
不可變的對象是一旦創建就無法更改的對象。您可以使用private
訪問修飾符和final
關鍵字來創建此類對象。
如果一個不可變的對象被序列化,它的原始字節可以被修改,以便在反序列化時該對象不再是相同的。
這不能完全防止。加密,校驗和和CRC將有助於防止這種情況發生。
'私人'是不是真的需要。 'public final String foo =「foo」'不會使類變爲可變的。這只是糟糕的封裝。 – zapl
在那個特定情況下沒有。但那只是因爲String對象本身是不可變的。我覺得一個真正不可變的對象不允許訪問它的內部數據,如果這些對象不是不可變的,那麼 –
取決於你的定義,我假設。在這裏做一個可變的表示該對象不能被重新分配或者它的內部狀態不能被改變了嗎? –
當您序列化一個對同一對象具有多個引用的對象圖時,序列化程序會注意到這一事實,因此反序列化的對象圖具有相同的結構。
例如,
int[] none = new int[0];
int[][] twoArrays = new int[] { none, none };
System.out.print(twoArrays[0] == twoArrays[1]);
將打印true
,如果你序列化和反序列化twoArrays
,那麼你將得到的,而不是陣列是不同的對象中的每個元素的相同的結果
int[][] twoDistinctArrays = new int[] { new int[0], new int[0] };
您可以利用此支持進行引用共享,以便在序列化條目之後製作字節以與私人幫助對象或數組共享引用,然後對其進行變異。
因此,一個不可序列化的類可以維護不變量 - 一個私有對象不會轉義 - 一個可序列化的類無法維護的變量。
通常的防禦措施是實現一個防禦性編碼的readObject()方法,使得那些反序列化的新對象(改變所使用的引用)。例如,如果對象有一個名爲'id'的Integer'字段,那麼''readObject()''方法會有'id = new Integer(id)'。請參閱http://video.javazone.no/talk/49302113以下兩分鐘21:21以瞭解更多信息。 –
您也可以使用ObjectInputStream.readUnshared()方法來防止混疊。這不足以保證安全;正如Bloch在* Effective Java *中指出的那樣,有人可以在序列化文件中更改已刪除的版本並搞亂不變量。更糟的是,有人可以用一個子類來替代一個類。你序列化一個'java.util.Date',並且有人用一個'my.bad.Date'子類替換它,它在訪問時會做一些令人討厭的事情。 –
@Slanec,謝謝你的指點。是的,你可以通過重新實現它的大塊來解決Java序列化的安全問題,但是當我需要安全地將字節轉換爲對象時,我放棄了Java的內置序列化,而是使用解析器生成器,以便可以靜態綁定方法這可以作爲解析的結果來調用。 –
通過將所有狀態信息保存在創建對象後無法更改的表單中,使其不可變。
Java在某些情況下不允許完美不變。
可串行化是你可以做的事情,但它並不完美,因爲在反序列化時必須重新創建一個對象的確切副本,並且可能不足以使用相同的構造函數來反序列化和創建首先是對象。這留下了一個洞。
有些事情要做:
- 無非是私人或最終性能。
- 構造函數設置對操作至關重要的任何屬性。
一些其他的事情要考慮:
- 靜態變量可能是一個壞主意,雖然靜態最終常量是沒有問題的。當課程被加載時,沒有辦法從外部設置它們,但是不能在以後再次設置它們。
- 如果傳遞給構造函數的其中一個屬性是對象,則調用方可以保持對該對象的引用,如果它不是不可變的,則更改該對象的某個內部狀態。這有效地改變了你的對象的內部狀態,這個狀態已經存儲了一個現在被修改的對象的副本。
- 理論上,某人可以採用序列化的形式並對其進行修改(或者只是從頭開始構建序列化形式),然後使用它反序列化,從而創建對象的修改版本。 (我認爲這在大多數情況下可能不值得擔心)。
- 你可以編寫自定義的序列化/反序列化代碼,對序列化表單進行簽名(或加密),以便修改是可檢測的。或者你可以使用某種形式的序列化表單傳輸,以保證它不會被更改。 (這裏假定你在傳輸過程中可以對序列化表單進行一些控制。)
- 有字節碼操縱器可以做任何他們想要的對象。例如,將setter方法添加到其他不可變對象。
簡單的答案是,在大多數情況下,只需按照這個答案頂部的兩條規則,這將足以處理您對不變性的需求。
您應該閱讀由Joshua Bloch編寫的Effective Java。有關於序列化的安全問題和建議如何正確設計你的課程的整個章節。
簡而言之:您應該瞭解一下readObject和readResolve方法。
更詳細的答案: 是的序列化可以打破不變性。
讓我們假設你有課期間(約書亞書它的例子):
private final class Period implements Serializable {
private final Date start;
private final Date end;
public Period(Date start, Date end){
this.start = new Date(start.getTime());
this.end = new Date(end.getTime());
if(this.start.compareTo(this.end() > 0)
throw new IllegalArgumentException("sth");
}
//getters and others methods ommited
}
它看起來很棒。這是不變的(你不能改變啓動和初始化結束後),優雅的,小的,線程等
但是......
你要記住,系列化是創建對象的另一種方式(它是不使用構造函數)。對象是從字節流中構建的。
考慮一下當有人(攻擊者)改變你的序列化字節數組的情況。如果他做這樣的事情,他可以打破你的條件開始<結束。此外,攻擊者有可能將對其Date對象的引用(傳遞到反序列化方法)引用(這是可變的,並且Period類的不可變性將被完全破壞)。
最好的防禦措施是不使用序列化,如果你不必。 如果您必須序列化您的課程,請使用序列化代理模式。
編輯(在kurzbot請求): 如果你想使用序列化代理,你必須在Period中添加靜態內部類。這個類對象將用於序列化而不是Period類對象。
期類寫了兩個新方法:
private Object writeReplace(){
return new SerializationProxy(this);
}
private void readObject(ObjectInputStream stream) throws InvalidObjectException {
throw new InvalidObjectException("Need proxy");
}
第一種方法替代默認序列化對象期間與SerializationProxy對象。第二保證攻擊者不會使用標準的readObject方法。
你應該寫writeObject方法爲SerializationProxy所以你可以使用:
private Object readResolve() {
return new Period(start, end);
}
在這種情況下,你只使用公共API和具有確定性週期類將保持一成不變。
您可以詳細說明序列化代理模式如何解決此問題嗎?我不確定我是否理解它如何抵禦你給出的情景。 – kurtzbot
我加了一些解釋。 –
正如其他人所說的,人們可能會認爲序列化會產生一個全新的對象,然後這個對象是不可變的,所以不,序列化不會破壞它,但我認爲我們必須對不變性有更大的瞭解。在回答這個問題之前考慮一下。
我認爲真正的答案完全取決於被序列化的類,以及所需的不變性水平,但由於面試官沒有給我們源代碼,我會拿出我自己的。我還想指出,只要人們開始討論不變性,他們就開始投擲關鍵詞 - 是的,這使得引用不可變,但它不是實現不變性的唯一方法。好吧,讓我們來看看一些代碼:
public class MyImmutableClass implements Serializable{
private double value;
public MyImmutableClass(double v){
value = v;
}
public double getValue(){ return value; }
}
是這個類可變因爲我實現了Serializable
?它是否可變因爲我沒有使用關鍵字final
?沒辦法 - 在每一個實際的意義上都是不變的,因爲我不會修改源代碼(即使你問我很好),但更重要的是,它是不可變的,因爲沒有外部類可以改變value
的值,沒有使用反射公開,然後修改它。通過該標記,我想你可以運行一些中間十六進制編輯器並手動修改RAM中的值,但這並不會使它比以前更易變。擴展類也不能修改它。當然,你可以擴展它,然後重寫getValue()
以返回不同的東西,但這樣做不會改變底層的value
。
我知道這可能會歪曲很多人的方式,但我認爲不可變性通常是純粹的語義 - 例如,對於從外部類中調用你的代碼的人來說是不可改變的,還是對於在你的主板上使用BusPirate的人是不可改變的?有很好的理由使用final
來幫助確保不變性,但我認爲這一點在很多論點中都被誇大了。僅僅因爲JVM被允許在引擎蓋下做一些魔術來確保序列化的工作並不意味着你的應用程序所需的不變性水平會以某種方式被破壞。
污垢簡單的答案是
class X implements Serializable {
private final transient String foo = "foo";
}
領域foo將等於「foo」,如果是新創建的對象,但反序列化(和不訴諸搞鬼的時候將是無效的,你不會能夠分配它)。
正確!如果您從不序列化內部數據,則在序列化對象時不會破壞不變性。雖然是真的,但這不是很有用。 –
- 1. GWT序列化破壞?
- 2. JSON反序列化不反序列化?
- 3. 性狀和序列化/反序列化
- 4. 屬性序列化/反序列化
- 5. 如何序列化/反序列化屬性的通用列表?
- 6. 火腿不序列化+反序列化
- 7. 序列化/反序列化如何提高性能?
- 8. 如何使用泛型屬性序列化/反序列化類?
- 9. 反序列化反序列化接口
- 10. 序列化/反序列化DataContracts列表
- 11. Json.NET:序列化/反序列化陣列
- 12. 序列化/反序列化不同的屬性名稱?
- 13. 使一個屬性反序列化,但不可序列化
- 14. 多元素反序列化XML文件屬性 - 屬性不反序列化
- 15. C# - 對象列表的反序列化,不反序列化繼承的屬性
- 16. 反序列化XML屬性
- 17. DefaultValue屬性反序列化
- 18. 反序列化JSON改變
- 19. 反序列化cookie變量
- 20. 如何反序列化XML屬性
- 21. XML反序列化到我如何反序列化這樣的XML特性
- 22. 反序列化
- 23. 反序列化
- 24. 反序列化
- 25. 反序列化
- 26. 反序列化
- 27. 反序列化序列
- 28. Java可序列化 - 序列化/反序列化是否具有安全性?
- 29. 如何反序列化
- 30. 如何反序列化CommandResult?
我不是100%確定的,我從來沒有把太多的時間放入序列化之前,但它可能與'transient'關鍵字有關。如果你有,說一個MouseListener實例在一個不可變類中是暫態的,那麼通過序列化然後反序列化,你可以通過修改MouseListener實例很好地改變這個類。 – 2013-07-11 21:03:45
@Daft Punk - 使用transient關鍵字指定不需要序列化屬性 – DRastislav
我認爲序列化和反序列化會創建一個新對象,因此不會影響不變性。 – parsifal