自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

破解單例模式:反射、序列化與克隆攻擊的防御之道

開發(fā) 前端
對 EnumSingleton 文件進(jìn)行反編譯,可以發(fā)現(xiàn) EnumSingleton 繼承于 Enum,而 Enum 類確實(shí)沒有無參的構(gòu)造器,所以拋出 NoSuchMethodException。

可能有人看了我上一篇文章里幾種方式對比的表格,覺得枚舉有缺點(diǎn),為什么Joshua Bloch還推薦使用枚舉?

這就要提到單例的破解了。普通的單例模式是可以通過反射和序列化/反序列化來破解的,而Enum由于自身的特性問題,是無法破解的。當(dāng)然,由于這種情況基本不會出現(xiàn),因此我們在使用單例模式的時候也比較少考慮這個問題。

枚舉類是實(shí)現(xiàn)單例模式最好的方式

在單例模式的實(shí)現(xiàn)中,除去枚舉方法實(shí)現(xiàn)的單例模式,其它的實(shí)現(xiàn)都可以利用反射構(gòu)造新的對象,從而破壞單例模式,但是枚舉就不行,下面說說原因:
破壞單例的方式有 3 種,反射、克隆以及序列化,下面詳細(xì)介紹:

反射

常見的單例模式實(shí)現(xiàn)中,往往有一個私有的構(gòu)造函數(shù),防止外部程序的調(diào)用,但是通過反射可以輕而易舉的破壞這個限制:

public class DobleCheckSingleton {

    private DobleCheckSingleton(){}

    private static volatile DobleCheckSingleton dobleCheckSingleton;

    public static DobleCheckSingleton getSingleton(){
        if (dobleCheckSingleton == null){
            synchronized (DobleCheckSingleton.class){
                if (dobleCheckSingleton == null){
                    dobleCheckSingleton = new DobleCheckSingleton();
                }
            }
        }
        return dobleCheckSingleton;
    }

    public static void main(String[] args) {
        try {
            DobleCheckSingleton dobleCheckSingleton = DobleCheckSingleton.getSingleton();
            Constructor<DobleCheckSingleton> constructor = DobleCheckSingleton.class.getDeclaredConstructor();
            constructor.setAccessible(true);
            DobleCheckSingleton reflectInstance = constructor.newInstance();
            System.out.println(dobleCheckSingleton == reflectInstance);
        } catch (Exception e  e.printStackTrace();
        }
    }
}

輸出:false,單例被破壞

顯然,通過反射可以破壞所有含有無參構(gòu)造器的單例類,如可以破壞懶漢式、餓漢式、靜態(tài)內(nèi)部類的單例模式。

但是反射無法破壞通過枚舉實(shí)現(xiàn)的單例模式,利用反射構(gòu)造新的對象,由于 enum 沒有無參構(gòu)造器,結(jié)果會拋出 NoSuchMethodException 異常。

public enum EnumSingleton {

    INSTANCE;

    public static void main(String[] args) {
        try {
            EnumSingleton enumSingleton = EnumSingleton.INSTANCE;
            // 獲取無參的構(gòu)造函數(shù)
            Constructor<EnumSingleton> constructor = null;
            constructor = EnumSingleton.class.getDeclaredConstructor();
            // 使用構(gòu)造函數(shù)創(chuàng)建對象
            constructor.setAccessible(true);
            EnumSingleton reflectInstance = constructor.newInstance();
            System.out.println(enumSingleton == reflectInstance);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

輸出:java.lang.NoSuchMethodException: singleton.EnumSingleton.<init>()
    at java.lang.Class.getConstructor0(Class.java:3082)
    at java.lang.Class.getDeclaredConstructor(Class.java:2178)
    at singleton.EnumSingleton.main(EnumSingleton.java:19)

枚舉安全的原因解釋:

對 EnumSingleton 文件進(jìn)行反編譯,可以發(fā)現(xiàn) EnumSingleton 繼承于 Enum,而 Enum 類確實(shí)沒有無參的構(gòu)造器,所以拋出 NoSuchMethodException。

枚舉類 EnumSingleton 反編譯結(jié)果
public final class singleton.EnumSingleton extends java.lang.Enum<singleton.EnumSingleton>

Enum 類的構(gòu)造方法
protected Enum(String name, int ordinal) {
        this.name = name;
        this.ordinal = ordinal;
}

進(jìn)一步,通過調(diào)用父類有參構(gòu)造器構(gòu)造枚舉實(shí)例對象,樣例程序又拋出 IllegalArgumentException 異常。

public enum EnumSingleton {

    INSTANCE;

    public EnumSingleton getInstance(){
        return INSTANCE;
    }

    public static void main(String[] args) {
        try {
            EnumSingleton enumSingleton = EnumSingleton.INSTANCE;
            // 獲取無參的構(gòu)造函數(shù)
            Constructor<EnumSingleton> constructor = null;
            constructor = EnumSingleton.class.getDeclaredConstructor(String.class,int.class);
            // 使用構(gòu)造函數(shù)創(chuàng)建對象
            constructor.setAccessible(true);
            EnumSingleton reflectInstance = constructor.newInstance("test",1);
            System.out.println(enumSingleton == reflectInstance);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

輸出:
java.lang.IllegalArgumentException: Cannot reflectively create enum objects
    at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
    at singleton.EnumSingleton.main(EnumSingleton.java:31)

因?yàn)?Constructor 的 newInstance 方法限定了 clazz 的類型不能是 enum,否則拋出異常。

@CallerSensitive
 public T newInstance(Object ... initargs)
     throws InstantiationException, IllegalAccessException,
            IllegalArgumentException, InvocationTargetException
 {
    ...
     if ((clazz.getModifiers() & Modifier.ENUM) != 0)
         throw new IllegalArgumentException("Cannot reflectively create enum objects");
     ...
 }

所以枚舉類不能通過反射構(gòu)建構(gòu)造函數(shù)的方式構(gòu)建新的實(shí)例。

序列化

先看看通過序列化破壞單例的例子,其中 Singleton 實(shí)現(xiàn)了 Serializable 接口,才有可能通過序列化破壞單例。

public class DobleCheckSingleton implements Serializable {

    private DobleCheckSingleton() {
    }

    private static volatile DobleCheckSingleton dobleCheckSingleton;

    public static DobleCheckSingleton getSingleton() {
        if (dobleCheckSingleton == null) {
            synchronized (DobleCheckSingleton.class) {
                if (dobleCheckSingleton == null) {
                    dobleCheckSingleton = new DobleCheckSingleton();
                }
            }
        }
        return dobleCheckSingleton;
    }

    public static void main(String[] args) {
        try {
            DobleCheckSingleton dobleCheckSingleton = DobleCheckSingleton.getSingleton();

            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("SerSingleton.obj"));
            oos.writeObject(dobleCheckSingleton);
            oos.flush();
            oos.close();

            FileInputStream fis = new FileInputStream("SerSingleton.obj");
            ObjectInputStream ois = new ObjectInputStream(fis);
            DobleCheckSingleton s1 = (DobleCheckSingleton) ois.readObject();
            ois.close();

            System.out.println(dobleCheckSingleton == s1);
   } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
輸出:false,單例被破壞

枚舉類實(shí)現(xiàn),枚舉類不實(shí)現(xiàn) Serializable 接口,都可以進(jìn)行序列化,并且返回原來的單例。

public enum EnumSingleton {

    INSTANCE;

    public static void main(String[] args) {
        try {
            EnumSingleton enumSingleton = EnumSingleton.INSTANCE;

            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("SerSingleton.obj"));
            oos.writeObject(enumSingleton);
            oos.flush();
            oos.close();

            FileInputStream fis = new FileInputStream("SerSingleton.obj");
            ObjectInputStream ois = new ObjectInputStream(fis);
            EnumSingleton s1 = (EnumSingleton) ois.readObject();
            ois.close();

            System.out.println(enumSingleton == s1);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

輸出:true

原因: 枚舉類的 writeObject 方法僅僅是將 Enum.name 寫到文件中,反序列化時,根據(jù) readObject 方法的源碼定位到 Enum 的 valueOf 方法,他會根據(jù)名稱返回原來的對象。

克隆

實(shí)現(xiàn) Cloneable 接口重寫 clone 方法,但是 Enum 類中 clone 的方法是 final 類型,無法重寫,也就不能通過克隆破壞單例。

public abstract class Enum<E extends Enum<E>>
        implements Comparable<E>, Serializable {
     protected final Object clone() throws CloneNotSupportedException {
        throw new CloneNotSupportedException();
    }
}

不用枚舉如何防止單例模式破壞

若實(shí)現(xiàn)了序列化接口,重寫 readResolve 方法即可,反序列化時將調(diào)用該方法返回對象實(shí)例。

public Object readResolve() throws ObjectStreamException {
    return dobleCheckSingleton;
}

通過反射破壞單例的場景,可以在構(gòu)造方法中判斷實(shí)例是否已經(jīng)創(chuàng)建,若已創(chuàng)建則拋出異常。

private Singleton(){
    if (instance !=null){
        throw new RuntimeException("實(shí)例已經(jīng)存在,請通過 getInstance()方法獲取");
    }
}

通過clone破壞單例的場景,可以重寫clone方法,返回已有單例對象。


責(zé)任編輯:武曉燕 來源: seven97
相關(guān)推薦

2011-03-04 09:25:51

Java序列化

2018-03-19 10:20:23

Java序列化反序列化

2012-04-13 10:45:59

XML

2022-09-29 08:39:37

架構(gòu)

2023-12-13 13:49:52

Python序列化模塊

2013-03-11 13:55:03

JavaJSON

2011-06-01 15:05:02

序列化反序列化

2022-08-06 08:41:18

序列化反序列化Hessian

2023-11-20 08:44:18

數(shù)據(jù)序列化反序列化

2016-10-09 09:37:49

javascript單例模式

2009-06-14 22:01:27

Java對象序列化反序列化

2009-08-24 17:14:08

C#序列化

2011-06-01 14:26:11

序列化

2015-05-20 10:05:10

Ceph分布式文件系統(tǒng)序列化

2012-11-30 14:54:48

2009-08-06 11:16:25

C#序列化和反序列化

2011-05-18 15:20:13

XML

2021-09-07 10:44:35

異步單例模式

2021-03-02 08:50:31

設(shè)計單例模式

2021-06-03 14:14:25

無文件攻擊PowerShell惡意攻擊
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號