引言
雖然國家規定了計劃生育,但是仍然有很多人。冒著坐牢、推房子、丟工作的風險,偷偷的生下自己的寶寶!對程式而言,同樣也可以,雖然我們私有化的構造方法,但是同樣有很多種方式去建立新的物件。
主要原理就是,我們獲取單例類的構造方法,然後把私有訪問許可權開啟。然後進行建立物件。具體程式碼如下:
首先搞一個單例類:
/**
* 先搞一個單例類。
* @ClassName : Eager //餓漢單例
* @Description : //類載入的時候,進行初始化。
*/
public class Hungry {
private static Hungry instance = new Hungry();
/**
* 私有構造方法
*/
private Hungry() {
}
/**
* 外部過去例項的通道
* @return
*/
public static Hungry getInstance() {
return instance;
}
}
進行破解:
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
/**
* @ClassName : 破解
* @Description :此類演示破解的過程
*/
public class Crack {
public static void main(String[] args) {
// 先去校驗我們的單例寫的是是否成功,我們沒有重寫equals方法,所有比較是物件的地址值。
// 此處我們獲取了兩次,然後進行比較。
if (Hungry.getInstance()==Hungry.getInstance()){
// 輸出此句話,說明我們的單例設計模式是成功的。
System.out.println("單例設計模式是成功的");
}
// 正常獲取單例物件
Hungry instance = Hungry.getInstance();
try {
// 反射獲取構造方法
Constructor<Hungry> declaredConstructor = Hungry.class.getDeclaredConstructor();
// 破解private訪問許可權
declaredConstructor.setAccessible(true);
// 使用構造方法建立物件
Hungry hungry = declaredConstructor.newInstance();
// 將我們反射獲取的物件跟正常渠道獲取的單例進行對比
System.out.println(instance==hungry);
// 如果輸出的是false,說明就是破解成功了。
} catch (NoSuchMethodException e) {
System.out.println("獲取構造方法異常");
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
既然我們反射可以破解我們的單例類,那我們應該知道如何加強防護。
/**
* @ClassName : Eager //餓漢單例
* @Description : //類載入的時候,進行初始化。
*/
public class Hungry {
private static Hungry instance = new Hungry();
/**
* 私有構造方法
*/
private Hungry() {
// 在此處加強防護,針對instance進行判空處理。
if (instance != null) {
throw new IllegalArgumentException("物件不允許反射建立");
}
}
/**
* 外部過去例項的通道
* @return
*/
public static Hungry getInstance() {
return instance;
}
}
還是老樣子,首先搞一個單例物件,此例項物件要實現序列化介面
/**
* @ClassName : Eager //餓漢單例
* @Description : //類載入的時候,進行初始化。
*/
public class Hungry {
private static Hungry instance = new Hungry();
/**
* 私有構造方法
*/
private Hungry() {
// 在此處加強防護,針對instance進行判空處理。
if (instance != null) {
throw new IllegalArgumentException("物件不允許反射建立");
}
}
/**
* 外部過去例項的通道
* @return
*/
public static Hungry getInstance() {
return instance;
}
}
使用序列化技術進行破解~
import java.io.*;
/**
* @ClassName : SerializableCrack 序列化破解demo
* @Description : 演示序列化破解單例設計模式
*/
public class SerializableCrack {
public static void main(String[] args) {
// 同樣,我們先驗證我們的單例是成功的。
if (Hungry.getInstance() == Hungry.getInstance()) {
System.out.println("單例設計模式是成功的!");
}
// 首先,我們獲取一個物件
Hungry hungry = Hungry.getInstance();
// 對物件進行序列化的文字中
try {
// 建立一個物件序列化流。我們將物件輸出到1.txt文字中,等後續讀取使用
ObjectOutputStream objectOutput = new ObjectOutputStream(new FileOutputStream(new File("1.txt")));
// 輸出物件
objectOutput.writeObject(hungry);
// 建立一個物件序列化讀取流。
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(new File("1.txt")));
// 讀取物件
Hungry object = (Hungry)objectInputStream.readObject();
// 進行比較,如果輸出的是false,說明破解成功。
System.out.println(hungry==object);
} catch (Exception e) {
System.out.println("序列化失敗");
}
}
}
加強防護,可以使用readResolve方法,替換到反序列化的物件。
/**
* @ClassName : Eager //餓漢單例
* @Description : //類載入的時候,進行初始化。
*/
public class Hungry implements Serializable {
private static Hungry instance = new Hungry();
/**
* 私有構造方法
*/
private Hungry() {
// 在此處加強防護,針對instance進行判空處理。
if (instance != null) {
throw new IllegalArgumentException("物件不允許反射建立");
}
}
/**
* 外部過去例項的通道
* @return
*/
public static Hungry getInstance() {
return instance;
}
/**
* 此方法可以替換到反序列化的物件。
*/
Object readResolve() throws ObjectStreamException {
return instance;
}
}
差不多就這個樣子了。下一篇講工廠設計模式~
本作品採用《CC 協議》,轉載必須註明作者和本文連結