第3條講述了 Singleton 模式,并且給出了以下這個 Singleton 類的示例。
成都創(chuàng)新互聯(lián)專注于企業(yè)全網(wǎng)營銷推廣、網(wǎng)站重做改版、蘭山網(wǎng)站定制設(shè)計、自適應(yīng)品牌網(wǎng)站建設(shè)、H5開發(fā)、商城系統(tǒng)網(wǎng)站開發(fā)、集團(tuán)公司官網(wǎng)建設(shè)、成都外貿(mào)網(wǎng)站建設(shè)、高端網(wǎng)站制作、響應(yīng)式網(wǎng)頁設(shè)計等建站業(yè)務(wù),價格優(yōu)惠性價比高,為蘭山等各大城市提供網(wǎng)站開發(fā)制作服務(wù)。這個類限制了對其構(gòu)造器的訪問,確保永遠(yuǎn)只創(chuàng)建一個實例:
public class Singer{
public static final Singer INSTANCE = new Singer();
private Singer(){}
public static Singer getInstance(){
return INSTANCE;
}
}
但如果在這個類實現(xiàn)序列化,這種方式就不能保證安全了。序列化可以輕松突破這種機(jī)制,甚至,這種錯誤是在無意間的,并不是蓄意破壞。之前曾說過,序列化是一種獨立于構(gòu)造器的創(chuàng)建對象的機(jī)制,或者你可以變相地認(rèn)為序列化是一種以 byte[] 為參數(shù)的隱形構(gòu)造器。
演示:
public class Singer implements Serializable {
public static final Singer INSTANCE = new Singer();
private Singer(){}
public static Singer getInstance(){
return INSTANCE;
}
private Object readResolve(){
return INSTANCE;
}
}
public class Client {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Singer instance = Singer.getInstance();
//序列化
File file = new File("C:\\Users\\admin\\Desktop\\file_upload\\1.txt");
ser(instance,file);
//反序列化
byte[] bytes = readBytes(file);
Singer instance2 = deSer(bytes);
//比較兩個對象是否相同
System.out.println(instance == instance2);
}
static void ser(Singer s,File file) throws IOException {
FileOutputStream fos = new FileOutputStream(file);
ObjectOutputStream os = new ObjectOutputStream(fos);
os.writeObject(s);
os.close();
}
static Singer deSer(byte[] bytes) throws IOException, ClassNotFoundException {
ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(bytes));
Singer p = (Singer) in.readObject();
in.close();
return p;
}
static byte[] readBytes(File file) throws IOException {
FileInputStream in = null;
try {
in =new FileInputStream(file);
//當(dāng)文件沒有結(jié)束時,每次讀取一個字節(jié)顯示
byte[] data=new byte[in.available()];
in.read(data);
in.close();
return data;
} catch (IOException e) {
e.printStackTrace();
}finally {
in.close(); //關(guān)閉流
}
return null;
}
}
false
可以看出,反序列化的對象和單例獲得的對象并不相同,從而導(dǎo)致單例模式失效
readResole 特性允許你用 readObject 創(chuàng)建的實例代替另一個實例。對于一個正在被反序列化的對象,如果它的類定義了一個 readResolve 方法,并且具備正確的聲明,那么在反序列化之后,新建對象上的 readResolve 方法就會被調(diào)用。然后,該方法返回的對象應(yīng)用將被返回,取代新建的對象。在這個特性的絕大多數(shù)用法中,指向新建對象的引用不需要再被保留,因此立即成為垃圾回收的對象。
比如,使用以下的方法保證單例
private Object readResolve(){
return INSTANCE;
}
該方法忽略了被反序列化的對象,只返回該類初始化時創(chuàng)建的那個特殊的實例。因此,Singer 實例的序列化形式并不需要包含任何實際的數(shù)據(jù);所有的實例域都應(yīng)該聲明為 transient。
事實上,如果依賴 readResolve 進(jìn)行實例控制,帶有對象引用類型的所有實例域則都必須聲明為 transient。否則,攻擊者依然可以想辦法在 readResolve 運行之前,獲取反序列化的對象引用,得到一個單例之外的“副本”,類似于上一條中提到的 MutablePeriod攻擊。
這種攻擊有點復(fù)雜,但背后的思想?yún)s很簡單。如果 Singleton 包含一個非 transient 對象引用域,這個域的內(nèi)容就可以在 Singleton 的 readResolve 方法運行之前被反序列化。當(dāng)對象引用域的內(nèi)容被反序列化時,它就允許一個精心制作的流“盜用”指向最初被反序列化的 Singleton 引用。
實例演示:
import java.io.Serializable;
import java.util.Arrays;
public class Elvis implements Serializable {
public static final Elvis INSTANCE = new Elvis();
private Elvis(){}
private String[] favoriteSongs = {"紅顏如霜","發(fā)如雪"};
public void printFavorites(){
System.out.println(Arrays.toString(favoriteSongs));
}
private Object readResolve(){
return INSTANCE;
}
}
import java.io.Serializable;
public class ElvisStealer implements Serializable {
static Elvis impersonator;
private Elvis payload;
private Object readResolve(){
impersonator = payload;
return new String[]{"發(fā)如霜"};
}
private static final long serialVersionUID = 0;
}
import lombok.SneakyThrows;
import java.io.ByteArrayInputStream;
import java.io.ObjectInputStream;
public class ElvisImpersonator {
private static final byte[] bytes = new byte[]{
(byte) 0xac, (byte) 0xed,0x00,0x05,0x73,0x72,0x00,0x05,
0x45,0x6c,0x76,0x69,0x73, (byte) 0x84, (byte) 0xe6,
(byte) 0x93,0x33, (byte) 0xc3, (byte) 0xf4, (byte) 0x8b,
0x32,0x02,0x00,0x01,0x4c,0x00,0x0d,0x66,0x61,0x76,
0x6f,0x72,0x69,0x74,0x65,0x53,0x6f,0x6e,0x67,0x73,
0x74,0x00,0x12,0x4c,0x6a,0x61,0x76,0x61,0x2f,0x6c,
0x61,0x6e,0x67,0x2f,0x4f,0x62,0x6a,0x65,0x63,0x74,
0x3b,0x78,0x70,0x73,0x72,0x00,0x0c,0x45,0x6c,0x76,
0x69,0x73,0x53,0x74,0x65,0x61,0x6c,0x65,0x72,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x01,
0x4c,0x00,0x07,0x70,0x61,0x79,0x6c,0x6f,0x61,0x64,
0x74,0x00,0x07,0x4c,0x45,0x6c,0x76,0x69,0x73,0x3b,
0x78,0x70,0x71,0x00,0x7e,0x00,0x02
};
public static void main(String[] args) {
Elvis elvis = deser(bytes);
Elvis impersonator = ElvisStealer.impersonator;
elvis.printFavorites();
impersonator.printFavorites();
}
@SneakyThrows
static Elvis deser(byte[] bytes) {
ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(bytes));
Elvis p = (Elvis) in.readObject();
in.close();
return p;
}
}
通過將 favorites 聲明為 transient 可以解決這個問題。但如果確實要避免出現(xiàn)這種錯誤,最好的還是使用 枚舉來解決這種問題。
正如之前所提到過的:自從 jdk1.5之后,單例的最佳實現(xiàn)方式就是枚舉。當(dāng)然,readResolve 并非完全過時,有些情況可能不適合使用枚舉,這時候依然需要這種方案。但一定要注意,屬性設(shè)置為transient 或者 基本類型
你是否還在尋找穩(wěn)定的海外服務(wù)器提供商?創(chuàng)新互聯(lián)www.cdcxhl.cn海外機(jī)房具備T級流量清洗系統(tǒng)配攻擊溯源,準(zhǔn)確流量調(diào)度確保服務(wù)器高可用性,企業(yè)級服務(wù)器適合批量采購,新人活動首月15元起,快前往官網(wǎng)查看詳情吧
本文題目:序列化——對于實例控制,枚舉類型優(yōu)先于readResolve-創(chuàng)新互聯(lián)
標(biāo)題路徑:http://www.rwnh.cn/article32/dochpc.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供動態(tài)網(wǎng)站、小程序開發(fā)、移動網(wǎng)站建設(shè)、外貿(mào)網(wǎng)站建設(shè)、域名注冊、網(wǎng)站營銷
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)
猜你還喜歡下面的內(nèi)容