詳解Java序列化如何破壞單例模式
先看代碼:
public class Singleton {
private Singleton(){}
private static class SingletonInstance{
private static final Singleton instance = new Singleton();
}
public static Singleton getInstance(){
return SingletonInstance.instance;
}
}
我想應(yīng)該沒有不知道這行個(gè)類是干嘛的小伙伴了吧,這是單例模式的一種寫法。
單例模式是每一個(gè) Java boy 必須要掌握的設(shè)計(jì)模式,它所描述的是在某個(gè)進(jìn)程內(nèi),某個(gè)類有且僅有一個(gè)實(shí)例。我們知道要破壞單例模式就必須讓它創(chuàng)建多個(gè)對(duì)象。創(chuàng)建對(duì)象的方式無非就幾種:
- new
- clone
- 反射
- 反序列化
首先單例模式的構(gòu)造器一定是 private 的,所以 new 這種方式是無法破壞單例模式的。 而 clone 需要實(shí)現(xiàn) Cloneable 接口,單例模式誰如果實(shí)現(xiàn)了這個(gè)接口,請(qǐng)打死它。所以就剩下反射和反序列化了。本篇文章只討論反序列化。
反序列化破壞單例模式
與 clone 方式一樣,反序列化需要實(shí)現(xiàn) Serializable 接口,但是有小伙伴可能會(huì)說,誰會(huì)在單例模式中實(shí)現(xiàn) Serializable 接口咯,除非他瘋了,確實(shí)是這種情況,但是在實(shí)際情況中它并不是一定會(huì)避免的,有些類它就是一定要序列化。比如單例對(duì)象在不同環(huán)境或應(yīng)用實(shí)例之間的共享、持久化或狀態(tài)恢復(fù),當(dāng)然這些場景都屬于比較特殊的場景。
繼續(xù)用上面例子:
public class SerializableSingleton implements Serializable {
// 省略部分代碼
}
然后在對(duì)該類進(jìn)行序列化和反序列化
public class Test {
public static void main(String[] args) throws Exception {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("Singleton.txt"));
SerializableSingleton singleton = SerializableSingleton.getInstance();
oos.writeObject(singleton);
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("Singleton.txt"));
SerializableSingleton singleton1 = (SerializableSingleton) ois.readObject();
System.out.println("singleton = singleton1:" + (singleton == singleton1));
}
}
運(yùn)行結(jié)果
singleton = singleton1:false
通過對(duì) Singleton 進(jìn)行反序列化得到了一個(gè)全新的對(duì)象,這就破壞了 Singleton 的單例性了。我們看 readObject() 源碼就知道了。
public final Object readObject() throws IOException, ClassNotFoundException {
//...
int outerHandle = passHandle;
try {
Object obj = readObject0(false);
//...
return obj;
} finally {
//...
}
}
調(diào)用 readObject0() :
private Object readObject0(boolean unshared) throws IOException {
// ...
try {
switch (tc) {
// ...
case TC_OBJECT:
// readObject0()
return checkResolve(readOrdinaryObject(unshared));
// ...
}
} finally {
depth--;
bin.setBlockDataMode(oldMode);
}
}
readObject0() 是根據(jù)反序列化對(duì)象的不同執(zhí)行不同的方法來反序列化一個(gè)實(shí)例對(duì)象。我們這里是 Object,所以進(jìn)一步看 readOrdinaryObject()。
private Object readOrdinaryObject(boolean unshared) throws IOException {
// ...
Object obj;
try {
// 核心代碼
// 反射創(chuàng)建一個(gè)新對(duì)象
obj = desc.isInstantiable() ? desc.newInstance() : null;
} catch (Exception ex) {
throw (IOException) new InvalidClassException(
desc.forClass().getName(),
"unable to create instance").initCause(ex);
}
// ...
if (obj != null &&
handles.lookupException(passHandle) == null &&
desc.hasReadResolveMethod()) {
Object rep = desc.invokeReadResolve(obj);
if (unshared && rep.getClass().isArray()) {
rep = cloneArray(rep);
}
if (rep != obj) {
// Filter the replacement object
if (rep != null) {
if (rep.getClass().isArray()) {
filterCheck(rep.getClass(), Array.getLength(rep));
} else {
filterCheck(rep.getClass(), -1);
}
}
handles.setObject(passHandle, obj = rep);
}
}
return obj;
}
這段代碼最核心的地方就是:
obj = desc.isInstantiable() ? desc.newInstance() : null;
底層依然是利用反射的方式來創(chuàng)建一個(gè)新對(duì)象。
那么對(duì)于這種方式有什么保護(hù)措施沒?在 readOrdinaryObject() 最后面一段就已經(jīng)告知了:
if (obj != null && handles.lookupException(passHandle) == null &&
desc.hasReadResolveMethod()) {
//....
}
判斷反序列化的類是否已實(shí)現(xiàn)了 readResolve() ,如果有則會(huì)調(diào)用該方法,我們只需要在該方法里面返回原對(duì)象就可以了。驗(yàn)證下。
public class SerializableSingleton implements Serializable {
// ...
private Object readResolve() {
return SingletonInstance.instance;
}
}
執(zhí)行結(jié)果:
singleton = singleton1:true
執(zhí)行結(jié)果為 true,就說明序列化和反序列化出來的是同一個(gè)對(duì)象。
所以,要想防止單例被反序列化破壞,就讓單例實(shí)現(xiàn) readResolve()方法,返回同一個(gè)對(duì)象即可。
到此這篇關(guān)于詳解Java序列化如何破壞單例模式的文章就介紹到這了,更多相關(guān)Java序列化內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
cmd中javac和java使用及注意事項(xiàng)詳解
這篇文章主要介紹了cmd中javac和java使用及注意事項(xiàng)詳解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-07-07
Eclipse 出現(xiàn)Failed to load JavaHL Library解決方法
這篇文章主要介紹了Eclipse 出現(xiàn)Failed to load JavaHL Library解決方法的相關(guān)資料,今天使用Eclipse 時(shí)出現(xiàn)以上錯(cuò)誤,本文說明如何更更正,需要的朋友可以參考下2016-11-11
Java時(shí)間處理第三方包Joda?Time使用詳解
這篇文章主要為大家介紹了Java時(shí)間處理第三方包Joda?Time使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07
@Accessors(chain = true)注解報(bào)錯(cuò)的解決方案
這篇文章主要介紹了@Accessors(chain = true)注解報(bào)錯(cuò)的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-06-06
Spring Boot Admin實(shí)現(xiàn)服務(wù)健康預(yù)警功能
這篇文章主要介紹了Spring Boot Admin實(shí)現(xiàn)服務(wù)健康預(yù)警功能,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-05-05

