有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

java单例:如何通过反射停止创建实例

我知道在Java中,我们可以通过newclone()Reflectionserializing and de-serializing创建类的实例

我创建了一个简单的类来实现一个单例

我需要停止所有创建我的类实例的方法

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();
}

共 (6) 个答案

  1. # 1 楼答案

    通过在私有构造函数中添加以下检查

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

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

    您应该注意的另一件事是readResolve(..)方法,因为您的类实现了Serialiable。在那里,您应该返回现有实例

    但是使用单例的最简单方法是通过枚举-您不必担心这些事情

  3. # 3 楼答案

    作为单例的替代方案,您可以查看monostate pattern。然后,类的实例化不再是问题,您不必担心所列出的任何场景

    在monostate模式中,类中的所有字段都是static。这意味着类的所有实例共享相同的状态,就像单例一样。此外,这一事实对呼叫者是透明的;他们不需要知道像getInstance这样的特殊方法,他们只是创建实例并使用它们

    但是,就像单例一样,它是一种隐藏的全局状态;这是非常糟糕的

  4. # 4 楼答案

    除了enum解决方案之外,所有其他解决方案都可以通过反射解决 以下是关于如何绕过Dave G解决方案的两个示例:

    1:设置变量Singleton。单例到空值

    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);
    

    输出:

    • Singleton@17f6480
    • Singleton@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);
    

    输出:

    • Singleton@17f6480
    • Singleton@2d6e8792

    因此,如果您不想使用枚举,我可以想出两种方法:

    第一个选项:使用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")

    第二个选项:在singleton构造函数中,测试调用是否通过反射进行

    我建议您参考另一个Stackoverflow thread,以了解获取调用方类或方法的最佳方法

    因此,如果我在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:如果提议的解决方案存在变通办法,请让我知道

  5. # 5 楼答案

    如下定义单例:

    public enum Singleton {
        INSTANCE
    }
    
  6. # 6 楼答案

    签入构造函数如何:

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

    当然,这可能不是真正的停止-如果有人在混淆视听以访问私有成员,他们可以自己将字段设置为null。你必须问问自己,你想阻止什么,这有多值得

    (编辑:正如Bozho所提到的,即使通过反射,最终字段也可能无法设置。如果有一些方法通过JNI等进行设置,我不会感到惊讶……如果你给人们足够的访问权限,他们几乎可以做任何事……)