我佩服这用法(破坏单例)

127 阅读3分钟

​反射破坏单例

这里是一个 饿汉单例模式

package com.singleton.pattern.singleton.demo;

import java.io.Serializable;

/**
 * @Classname HungrySingleton
 * @Description 饿汉模式
 * @Date 2019/11/20 0020 15:23
 * @Created by 埔枘
 */
public class HungrySingleton implements Serializable {

    private static HungrySingleton hungrySingleton = new HungrySingleton();

    /**
     * 私有化 空构造
     */
    private HungrySingleton(){
        
    }

    public static HungrySingleton getSingleton(){
        return hungrySingleton;
    }
}

下简单演示 通过 反射破坏单例

单例的首要原则就是 构造方法私有,但是 通过反射 构造对象后,构造方法私有 变为无效,从而导致 单例被破坏

package com.singleton.pattern.singleton.sabotage;
import com.singleton.pattern.singleton.demo.HungrySingleton;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
/**
 *  破坏单例
*/
public class Sabottage {
    public static void main(String[] args) throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException {
        /**
         *  通过反射 破坏单例
*
         *  反射会强制执行 私有的构造方法 造成单例失效
*  懒加载,饿加载 都会存在此问题
*
         *  防止方案:
         *      从构造方法中 判断引用是否为空
*/
Class cls = HungrySingleton.class;
Constructor declaredConstructor = cls.getDeclaredConstructor();
declaredConstructor.setAccessible(true);//强吻
Object o1 = declaredConstructor.newInstance();
Object o2 = declaredConstructor.newInstance();
System.out.println(o1);
System.out.println(o2);
}
}

那么我们这么解决这个问题呢?

既然我们不能阻止 别人反射本类,那么我在你必经之路(构造方法 中判断一下对象是否被初始化了不就好了吗?)

在 构造方法中判断 实例是否已经初始化过了

package com.singleton.pattern.singleton.demo;
import java.io.Serializable;
/**
 * @Classname HungrySingleton
 * @Description 饿汉模式
* @Date 2019/11/20 0020 15:23
 * @Created by 埔枘
*/
public class HungrySingleton implements Serializable {

    private static HungrySingleton hungrySingleton = new HungrySingleton();
/**
     * 私有化 空构造
*/
private HungrySingleton(){
        if(hungrySingleton!=null){
            throw new RuntimeException("不能二次实例化对象");
}
    }

    public static HungrySingleton getSingleton(){
        return hungrySingleton;
}
}

可以看到 程序直接抛出了异常,阻止了 程序破坏单例

序列化破坏单例

序列化破坏单例

通过序列化把 实例 保存到文件系统中,再读到程序中,形成了 单例的破坏。

package com.singleton.pattern.singleton.sabotage;
import com.singleton.pattern.singleton.demo.HungrySingleton;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.InvocationTargetException;
/**
 *  破坏单例
*/
public class Sabottage {
    public static void main(String[] args) throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException {
        HungrySingleton s1 = null;
HungrySingleton s2 = HungrySingleton.getSingleton();
FileOutputStream fos = null;
        try {
            fos = new FileOutputStream("HungrySingleton.obj");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(s2);
oos.flush();
oos.close();
FileInputStream fis = new FileInputStream("HungrySingleton.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
s1 = (HungrySingleton)ois.readObject();
ois.close();
System.out.println(s1);
System.out.println(s2);
System.out.println(s1 == s2);
} catch (Exception e) {
            e.printStackTrace();
}
    }
}

为什么序列化对象会导致 单例被破坏呢?

查看 ObjectInputStream 的源码发现 如下代码

往下继续跟进去看 有一个 desc.isInstantiable() 的方法,如果条件符合那么就会初始化一个实例 赋值给 obj 并返回出去,形成新的对象。

继续看,这里主要判断了this.cons !=null

发现cons 指的是构造方法,也就是说只要你有构造方法 那么我就 new 一个新的实例返回出去,形成单例的破坏

那么怎么防止 序列化带来的 单例破坏问题呢?

我们继续看源码,

看到 这行代码

执行传入对象的 readResolve 方法,但是我们的 单例里没有这个方法呀

Object rep = desc.invokeReadResolve(obj);

通过全局查找

我们加一个 readResolve 方法试试

package com.singleton.pattern.singleton.demo;
import java.io.Serializable;
/**
 * @Classname HungrySingleton
 * @Description 饿汉模式
* @Date 2019/11/20 0020 15:23
 * @Created by 埔枘
*/
public class HungrySingleton implements Serializable {

    private static HungrySingleton hungrySingleton = new HungrySingleton();
/**
     * 私有化 空构造
*/
private HungrySingleton(){
        if(hungrySingleton!=null){
            throw new RuntimeException("不能二次实例化对象");
}
    }

    public static HungrySingleton getSingleton(){
        return hungrySingleton;
}

    public Object readResolve(){
        return HungrySingleton.hungrySingleton;
}
}

再次执行 发现 成功 防止了 序列化问题 导致的 单例破坏。

总结

反射破坏单例

懒汉模式,饿汉模式,容器式单例都会受影响,都有构造方法 尽管设置了 私有,但反射技术 可以实现 “强吻”,枚举单例免疫(枚举没有构造)

    解决方案:在构造方法中判断 引用对象是否已经初始化过了。

序列化破坏单例

懒汉模式,饿汉模式,容器式单例都会受影响,都有构造方法 尽管设置了 私有,但反射技术 可以实现 “强吻”,枚举单例免疫(枚举没有构造)

    解决方案: 加上 readResolve() 方法

获取源代码