簡體   English   中英

單例:如何通過反射停止創建實例

[英]Singleton: How to stop create instance via Reflection

我知道在 Java 中,我們可以通過newclone()Reflection以及通過serializing and de-serializing來創建 Class 的實例。

我創建了一個實現單例的簡單類。

我需要一路停下來創建我的類的實例。

public class Singleton implements Serializable{
    private static final long serialVersionUID = 3119105548371608200L;
    private static final Singleton singleton = new Singleton();
    private Singleton() { }
    public static Singleton getInstance(){
        return singleton;
    }
    @Override
    protected Object clone() throws CloneNotSupportedException {
        throw new CloneNotSupportedException("Cloning of this class is not allowed"); 
    }
    protected Object readResolve() {
        return singleton;
    }
    //-----> This is my implementation to stop it but Its not working. :(
    public Object newInstance() throws InstantiationException {
        throw new InstantiationError( "Creating of this object is not allowed." );
    }
}

在這個類中,我設法通過newclone()serialization停止了類實例,但是我無法通過反射來停止它。

我創建對象的代碼是

try {
    Class<Singleton> singletonClass = (Class<Singleton>) Class.forName("test.singleton.Singleton");
    Singleton singletonReflection = singletonClass.newInstance();
} catch (ClassNotFoundException e) {
    e.printStackTrace();
} catch (InstantiationException e) {
    e.printStackTrace();
} catch (IllegalAccessException e) {
    e.printStackTrace();
}

通過在您的私有構造函數中添加以下檢查

private Singleton() {
    if( singleton != null ) {
        throw new InstantiationError( "Creating of this object is not allowed." );
    }
}

像這樣定義單例:

public enum Singleton {
    INSTANCE
}

如何檢查構造函數:

private Singleton() {
    if (singleton != null) {
        throw new IllegalStateException("Singleton already constructed");
    }
}

當然,這可能並沒有真正阻止它 - 如果有人在使用反射來訪問私有成員,他們可能能夠自己將該字段設置為 null。 不過,您必須問問自己,您正在努力預防什么,以及它的價值。

(編輯:正如 Bozho 所提到的,即使通過反射,最終字段也可能無法設置。如果有某種方式通過 JNI 等進行設置,我不會感到驚訝......如果你給人們足夠的訪問權限,他們將能夠幾乎可以做任何事情...)

private Singleton() { 
    if (Singleton.singleton != null) {
        throw new RuntimeException("Can't instantiate singleton twice");
    }
}

您應該注意的另一件事是readResolve(..)方法,因為您的類實現了Serialiable 在那里你應該返回現有的實例。

但是使用單例的最簡單方法是通過枚舉——你不用擔心這些事情。

除了 enum 解決方案,所有其他解決方案都可以通過 Reflexion 解決這些是關於如何解決 Dave G 解決方案的兩個示例:

1:將變量 Singleton.singleton 設置為 null

Constructor<?>[] constructors = Singleton.class.getDeclaredConstructors();
Constructor theConstructor = constructors[0];
theConstructor.setAccessible(true);
Singleton instance1 = (Singleton) theConstructor.newInstance();

Singleton.getInstance();

Field f1 = Singleton.class.getDeclaredField("singleton");
f1.setAccessible(true);
f1.set(f1, null);
Singleton instance2 = (Singleton) theConstructor.newInstance();

System.out.println(instance1);
System.out.println(instance2);

輸出 :

  • 單身@17f6480
  • 單身@2d6e8792

2:不調用getInstance

Constructor<?>[] constructors = Singleton.class.getDeclaredConstructors();
Constructor theConstructor = constructors[0];
theConstructor.setAccessible(true);
Singleton instance1 = (Singleton) theConstructor.newInstance();
Singleton instance2 = (Singleton) theConstructor.newInstance();

System.out.println(instance1);
System.out.println(instance2);

輸出 :

  • 單身@17f6480
  • 單身@2d6e8792

因此,如果您不想使用 Enum ,我可以想到兩種方法:

第一個選項:使用 securityManager :

它防止使用未經授權的操作(從類外部調用私有方法......)

所以你只需要在其他答案提出的單例構造函數中添加一行

private Singleton() {
    if (singleton != null) {
        throw new IllegalStateException("Singleton already constructed");
    }
    System.setSecurityManager(new SecurityManager());
}

它的作用是防止調用setAccessible(true)所以當你想調用它時:

Constructor<?>[] constructors = Singleton.class.getDeclaredConstructors();
Constructor theConstructor = constructors[0];
theConstructor.setAccessible(true);

將發生此異常: java.security.AccessControlException: access denied ("java.lang.RuntimePermission" "createSecurityManager")

第二個選項:在單例構造函數中,測試調用是否通過 Reflexion 進行

我向您推薦這個其他Stackoverflow 線程,以獲得獲取調用者類或方法的最佳方式。

所以如果我在 Singleton 構造函數中添加這個:

String callerClassName = new Exception().getStackTrace()[1].getClassName();
System.out.println(callerClassName);

我這樣稱呼它:

Constructor<?>[] constructors = Singleton.class.getDeclaredConstructors();
Constructor theConstructor = constructors[0];
theConstructor.setAccessible(true);
Singleton instance1 = (Singleton) theConstructor.newInstance();

輸出將是: jdk.internal.reflect.DelegatingConstructorAccessorImpl

但是如果我定期調用它(實例化公共構造函數或調用沒有反射的方法),則會打印調用方法的類的名稱。 所以例如我有:

public class MainReflexion {
    public static void main(String[] args) {
        Singleton.getInstance();
    }
}

callerClassName 將是MainReflexion ,因此輸出將是MainReflexion


PS :如果建議的解決方案存在解決方法,請告訴我

我們可以使用靜態嵌套類來打破它

請按照以下代碼 100% 正確,我測試過

package com.singleton.breakable;

import java.io.Serializable;

class SingletonImpl implements Cloneable, Serializable {

    public static SingletonImpl singleInstance = null;

    private SingletonImpl() {

    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return singleInstance;
    };

    public Object readResolve() {
        return SingletonImpl.getInstance(); // 
    }

    public static SingletonImpl getInstance() {

        if (null == singleInstance) {
            singleInstance = new SingletonImpl();
        }
        return singleInstance;
    }

}


package com.singleton.breakable;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;

class FullySingletonClass {

    public static void main(String[] args) {

        SingletonImpl object1 = SingletonImpl.getInstance();
        System.out.println("Object1:" + object1);

        try {
            FileOutputStream fos = new FileOutputStream("abc.txt");
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(object1);

            FileInputStream fis = new FileInputStream("abc.txt");
            ObjectInputStream ois = new ObjectInputStream(fis);
            SingletonImpl object2 = (SingletonImpl) ois.readObject();
            System.out.println("Object2" + object2);

        } catch (Exception e) {
            // TODO: handle exception
        }
        try {
            Constructor[] constructors = SingletonImpl.class.getDeclaredConstructors();
            for (Constructor constructor : constructors) {
                // Below code will not destroy the singleton pattern
                constructor.setAccessible(true);
                SingletonImpl Object3 = (SingletonImpl) constructor.newInstance();
                System.out.println("Object3: Break through Reflection:" + Object3);
                break;
            }
        } catch (Exception ew) {

        }

    }
}

**OUTPUT**
Object1:com.singleton.breakable.SingletonImpl@15db9742
Object2com.singleton.breakable.SingletonImpl@15db9742
Object3: Break through Reflection:com.singleton.breakable.SingletonImpl@33909752

作為單例的替代方案,您可以查看monostate 模式 然后,您的類的實例化不再是問題,您不必擔心您列出的任何場景。

在單態模式中,類中的所有字段都是static 這意味着該類的所有實例共享相同的狀態,就像單例一樣。 而且,這個事實對調用者是透明的; 他們不需要知道像getInstance這樣的特殊方法,他們只需創建實例並使用它們。

但是,就像單例一樣,它是一種隱藏的全局狀態; 這是非常糟糕的

我下面的代碼將起作用..

class Test {

    static private Test t = null;
    static {
        t = new Test();
    }

    private Test(){}

    public static Test getT() {
        return t;
    }

    public String helloMethod() {
        return "Singleton Design Pattern";
    }
}


public class MethodMain {

    public static void main(String[] args) {
        Test t = Test.getT();
        System.out.println(t.helloMethod());
    }
}

輸出:單例設計模式

請注意,從 Java 8 開始,根據我的檢查,您不能通過反射實例化單例,只要它具有私有構造函數。

你會得到這個例外:

Exception in thread "main" java.lang.IllegalAccessException: Class com.s.Main can not access a member of class com.s.SingletonInstance with modifiers "private"
at sun.reflect.Reflection.ensureMemberAccess(Unknown Source)
at java.lang.Class.newInstance(Unknown Source)
at com.s.Main.main(Main.java:6)

完美的單例類,可以避免在序列化、克隆和反射過程中創建實例。

import java.io.Serializable;

public class Singleton implements Cloneable, Serializable {

    private static final long serialVersionUID = 1L;
    private static volatile Singleton instance;

    private Singleton() {
        if (instance != null) {
            throw new InstantiationError("Error creating class");
        }
    }

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {

                if (instance == null) {
                    return new Singleton();
                }
            }
        }
        return null;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        throw new CloneNotSupportedException();
    }

    Object readResolve() {
        return Singleton.getInstance();
    }

}

為了克服反射引起的問題,使用了枚舉,因為 java 在內部確保枚舉值僅實例化一次。 由於 java 枚舉是全局可訪問的,因此它們可用於單例。 它唯一的缺點是它不靈活,即它不允許延遲初始化。

public enum Singleton {
 INSTANCE
}

public class ReflectionTest 
{

    public static void main(String[] args)
    {
        Singleton instance1 = Singleton.INSTANCE;
        Singleton instance2 = Singleton.INSTANCE;
    System.out.println("instance1 hashcode- "
                                      + instance1.hashCode());
        System.out.println("instance2 hashcode- "
                                      + instance2.hashCode());
    }
}

JVM 在內部處理枚舉構造函數的創建和調用。 由於枚舉不向程序提供其構造函數定義,因此我們也無法通過反射訪問它們。

有關更多詳細信息,請參閱帖子。

延遲初始化的方法:

  private static Singleton singleton;

  public static Singleton getInstance() {
    if(singleton==null){
      singleton= new Singleton();
    }
    return singleton;
  }


private Singleton() {
    if (Singleton.singleton != null) {
      throw new InstantiationError("Can't instantiate singleton twice");
    }
    Singleton.singleton = this;
}

即使您決定在任何 getInstance 調用之前使用反射創建實例,這種方法也有效

Here Reflection not work     

    package com.singleton.nonbreakable;

        import java.io.FileInputStream;
        import java.io.FileOutputStream;
        import java.io.ObjectInputStream;
        import java.io.ObjectOutputStream;
        import java.lang.reflect.Constructor;

        class FullySingletonClass {

            public static void main(String[] args) {

                SingletonImpl object1 = SingletonImpl.getInstance();
                System.out.println("Object1:" + object1);

                try {
                    FileOutputStream fos = new FileOutputStream("abc.txt");
                    ObjectOutputStream oos = new ObjectOutputStream(fos);
                    oos.writeObject(object1);

                    FileInputStream fis = new FileInputStream("abc.txt");
                    ObjectInputStream ois = new ObjectInputStream(fis);
                    SingletonImpl object2 = (SingletonImpl) ois.readObject();
                    System.out.println("Object2" + object2);

                } catch (Exception e) {
                    // TODO: handle exception
                }
                try {
                    Constructor[] constructors = SingletonImpl.class.getDeclaredConstructors();
                    for (Constructor constructor : constructors) {
                        // Below code will not destroy the singleton pattern
                        constructor.setAccessible(true);
                        SingletonImpl Object3 = (SingletonImpl) constructor.newInstance();
                        System.out.println("Object3:" + Object3);
                        break;
                    }
                } catch (Exception ew) {

                }

            }
        }


    package com.singleton.nonbreakable;

    import java.io.Serializable;

    class SingletonImpl implements Cloneable, Serializable {

        public static SingletonImpl singleInstance = null;

        private static class SingletonHolder {
            public static SingletonImpl getInstance() {
                if (null == singleInstance) {
                    singleInstance = new SingletonImpl();
                }
                return singleInstance;
            }
        }

        private SingletonImpl() {

        }

        @Override
        protected Object clone() throws CloneNotSupportedException {
            return singleInstance;
        };

        public Object readResolve() {
            System.out.println("Executing readResolve again");
            return SingletonImpl.getInstance(); // FIXME
        }

        public static SingletonImpl getInstance() {

            return SingletonHolder.getInstance();
        }

    }

    Output : 
    Object1:com.singleton.nonbreakable.SingletonImpl@15db9742
    Executing readResolve again
    Object2com.singleton.nonbreakable.SingletonImpl@15db9742

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM