シングルトンパターン

シングルトンパターンのセキュリティ

シングルトンはわれわれはよく知っていると思うが、この記事ではシングルトンの記述方法に関するものではない。

この記事では、シングルトンを破棄する方法を示します。そのため、シングルトンをアンチパターンを紹介します。

単純なシングルトンの例:

public class Singleton {
    private static final Singleton INSTANCE = new Singleton();

    private String name;

    public String getName() {
        return this.name;
    }

    private Singleton() {
        this.name = "Neo";
    }

    public static Singleton getInstance() {
        return INSTANCE;
    }
}

上記は比較的単純なシングルトンのパターンですが、私たちはクライアントの呼び出しを見ます:

public class APP {
    // Singleton intance = new Singleton();

    Singleton instance = Singleton.getInstance();
    System.out.println(instance.getName());
}

反射によってシングルトンを破壊する

原則は簡単で、反射によってその構造を得、インスタンスを再生成する。

class APP {
    public static void main(String[] args) throws Exception {
        Singleton instance1 = Singleton.getInstance();

        Constructor constructor = Singleton.class.getDeclaredConstructor();
        constructor.setAccessible(true);
        Singleton instance2 = (Singleton) constructor.newInstance();

        System.out.println(instance1 == instance2); // false
    }
}

明らかに、良いシングルケースは単一であり、上記のプログラムの結果は確かです:false

反射による損傷を防ぐ

あなたが単一のケースを避けたい場合に列挙を提供するJavaは、例えば破壊を反映しています:

public enum Singleton {
    INSTANCE;// 这里只有一项

    private String name;

    Singleton() {
        this.name = "Neo";
    }
    public static Singleton getInstance() {
        return INSTANCE;
    }

    public String getName() {
        return this.name;
    }
}

今度は、リフレクションによってクラスのコンストラクタを再構築したい場合:

Constructor constructor = Singleton.class.getDeclaredConstructor();

NoSuchMethodExceptionがスローされます。

Exception in thread "main" java.lang.NoSuchMethodException: com.javadoop.Singleton.<init>()
    at java.lang.Class.getConstructor0(Class.java:3082)
    at java.lang.Class.getDeclaredConstructor(Class.java:2178)
    at com.javadoop.singleton.APP.main(APP.java:11)

列挙の場合、JVMは、インスタンスを作成するときにJVMによってコンストラクタが呼び出されるインスタンスを自動的に作成します。

コード内でenumクラスのコンストラクタを取得することはできません。

シリアライゼーションによって破壊される

次に、シリアライズ、デシリアライズの別の方法について説明します。

シリアライゼーションはjavaオブジェクトをバイトストリームに変換し、デシリアライゼーションはバイトストリームからjavaオブジェクトに変換します。

class APP {
    public static void main(String[] args) throws ... {
        Singleton instance1 = Singleton.getInstance();

        Constructor constructor = Singleton.class.getDeclaredConstructor();
        constructor.setAccessible(true);
        Singleton instance2 = (Singleton) constructor.newInstance();

        // instance3 将从 instance1 序列化后,反序列化而来
        Singleton instance3 = null;
        ByteArrayOutputStream bout = null;
        ObjectOutputStream out = null;
        try {
            bout = new ByteArrayOutputStream();
            out = new ObjectOutputStream(bout);
            out.writeObject(instance1);

            ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());
            ObjectInputStream in = new ObjectInputStream(bin);
            instance3 = (Singleton) in.readObject();
        } catch (Exception e) {
        } finally {
            // close bout&out
        }

        // 显然,instance3 和 instance1 不是同一个对象了
        System.out.println(instance1 == instance3); // false
    }
}

instance1 == instance3とfalseが返されることは間違いありません。

シリアライゼーションのダメージを防ぐ

シリアライゼーションの前に、クラスを追加する必要がありimplements Serializableます。

私たちがする必要があるのは、クラスにreadResolve()メソッドを追加してインスタンスを返すことだけです。

public class Singleton implements Serializable {
    private static final Singleton INSTANCE = new Singleton();

    private String name;

    public String getName() {
        return this.name;
    }

    private Singleton() {
        this.name = "Neo";
    }

    public static Singleton getInstance() {
        return INSTANCE;
    }

    // 看这里
    public Object readResolve() throws ObjectStreamException {
        return INSTANCE;
    }
}

今回はtrueを返した。

非直列化時間では、JVMは自動的にこのメソッドをreadResolve()で呼び出すため、このメソッドでストリームから逆直列化されたオブジェクトを置き換えることができます。