[java設計模式]破解單例設計模式,禁止偷生、超生!

小杆子發表於2021-01-23

引言

雖然國家規定了計劃生育,但是仍然有很多人。冒著坐牢、推房子、丟工作的風險,偷偷的生下自己的寶寶!對程式而言,同樣也可以,雖然我們私有化的構造方法,但是同樣有很多種方式去建立新的物件。

[java設計模式]破解單例設計模式,禁止偷生、超生!

主要原理就是,我們獲取單例類的構造方法,然後把私有訪問許可權開啟。然後進行建立物件。具體程式碼如下:
首先搞一個單例類:

/**
 * 先搞一個單例類。
 * @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 協議》,轉載必須註明作者和本文連結

相關文章