【Java+设计模式】通过反射机制/序列化机制破解单例模式,及其解决方法

单例模式

关于单例模式,可以戳这篇文章:《【设计模式】单例模式(Singleton Pattern)

下面这是一个经典的懒汉式单例模式实现。

public class Singleton {
	// 1.在类中添加一个私有静态成员变量用于保存唯一实例
    private static Singleton instance;
    // 2.将默认构造方法设置为私有,这样它就不能被new了
    private Singleton() { }
	// 3.写一个公有静态成员方法,暴露给外部用于获取唯一实例
    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}
public class SingletonTest {
    public static void main(String[] args) throws Exception {
		Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println(instance1 == instance2);		// true
    }
}

 
 

反射机制破解单例模式

单例模式的实现中有一个核心步骤,将无参构造方法设置为私有,防止被外部访问到,从而new不出对象。

那么事情就变得非常简单——反射机制可以调用构造方法对象,且可以跳过访问权限检查,进行暴力反射。

public class DestroySingletonByReflect {
    public static void main(String[] args) throws Exception {
		// 加载类
        Class.forName("Singleton");
        // 通过反射获取无参构造方法对象
        Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
        // 跳过权限检查,进行暴力反射
        constructor.setAccessible(true);
        // 通过无参构造方法对象获得实例
        Singleton instance1 = constructor.newInstance();
        Singleton instance2 = constructor.newInstance();
        // 获取的还是同一个实例吗?
        System.out.println(instance1 == instance2);		// false
    }
}

如何避免单例模式被反射机制破解?

也很好理解,反射机制破解单例模式的本质是对无参构造方法(对象)的调用。我们给无参构造方法也加上单例模式的逻辑即可。

public class Singleton {
    private static Singleton instance;
    private Singleton() {
    	// 给无参构造方法也加上单例模式的逻辑
    	if (instance == null) {
            throw new RuntimeException();
        }
    }
    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

 
 

序列化机制破解单例模式

序列化/序列化经常帮助我们曲线救国(必须Java对象的深拷贝),但它也有做坏事的时候。

想一想,如果我们把单例对象序列化后存进文件,然后反序列化出来,得到的还是原先的那个单例对象吗?

public class DestroySingletonBySerialize {
    public static void main(String[] args) throws Exception {

        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();

        System.out.println(instance1 == instance2);		// true

		// 序列化
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(new File("/Users/samarua/Documents/object.txt")));
        objectOutputStream.writeObject(instance2);

		// 反序列化
        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(new File("/Users/samarua/Documents/object.txt")));
        Singleton instance3 = (Singleton) objectInputStream.readObject();

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

如何避免单例模式被序列化机制破解?

反序列化时永远不会调用构造方法,所以之前的反制方法失效了。这里,利用了序列化/反序列化机制的一个细节,在反序列化返回对象之前,会先看看有没有 readResolve() 方法,如果有,则返回该方法所返回的对象。

class Singleton implements Serializable {
    private static Singleton instance;
    private Singleton() { }
    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
    // 反序列时通过该方法返回了instance实例
    private Object readResolve() {
        return instance;
    }
}

 
 
 
 
 

 
 
 
 
 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值