ASP站长网单例模式,我想大家再熟悉不过了,不过本文不是介绍单例模式该怎么写的。
 
本文来说说怎么破坏一个单例,让你写的单例变成一个假的单例。当然,本文也会给出怎么进行防守的方法。
 
一个简单的单例
来一个简单的单例模式例子:
 
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 {    // 由于构造方法上加了 private 修饰,所以我们已经不能通过 ‘new’ 来产生实例了
    // 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
    }
}

dawei

【声明】:九江站长网内容转载自互联网,其相关言论仅代表作者个人观点绝非权威,不代表本站立场。如您发现内容存在版权问题,请提交相关链接至邮箱:bqsm@foxmail.com,我们将及时予以处理。