欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 文旅 > 美景 > 设计模式:序列化如何破坏单例?终极防御方案

设计模式:序列化如何破坏单例?终极防御方案

2025/6/18 14:58:02 来源:https://blog.csdn.net/weixin_47088026/article/details/148615606  浏览:    关键词:设计模式:序列化如何破坏单例?终极防御方案

单例模式

序列化对于单例的破坏

/*** 序列化对单例的破坏**/
public class Test_Serializable {@Testpublic void test() throws Exception{//序列化对象输出流ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("tempFile.obj"));oos.writeObject(Singleton.getInstance());//序列化对象输入流File file = new File("tempFile.obj");ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));Singleton Singleton = (Singleton) ois.readObject();System.out.println(Singleton);System.out.println(Singleton.getInstance());//判断是否是同一个对象System.out.println(Singleton.getInstance() == Singleton);//false}
}/*** 单例类实现序列化接口*/
class Singleton implements Serializable {private volatile static Singleton singleton;private Singleton() {}public static Singleton getInstance() {if (singleton == null) {synchronized (Singleton.class) {if (singleton == null) {singleton = new Singleton();}}}return singleton;}}

输出结构为false
说明:通过对Singleton的序列化与反序列化得到的对象是一个新的对象,这就破坏了Singleton的单例性

解决方案

/**
* 解决方案:
* 程序会判断是否有readResolve方法,如果存在就在执行该方法,如果不存在--就创建一个对象
*/
private Object readResolve() {return singleton;
}

问题是出在ObjectInputputStream 的readObject 方法上, 我们来看一下ObjectInputStream的readObject的调用栈:
在这里插入图片描述

ObjectInputStream中readObject方法的代码片段

try {Object obj = readObject0(false); //最终会返回一个object对象,其实就是序列化对象return obj;
} finally {passHandle = outerHandle;if (closed && depth == 0) {clear();}
}

ObjectInputStream中readObject0方法的代码片段

private Object readObject0(boolean unshared) throws IOException {case TC_OBJECT: //匹配如果是对象return checkResolve(readOrdinaryObject(unshared));
}

readOrdinaryObject方法的代码片段

private Object readOrdinaryObject(boolean unshared)throws IOException{//此处省略部分代码Object obj;try {//通过反射创建的这个obj对象,就是本方法要返回的对象,也可以暂时理解为是ObjectInputStream的readObject返回的对象。//isInstantiable:如果一个serializable的类可以在运行时被实例化,那么该方法就返回true//desc.newInstance:该方法通过反射的方式调用无参构造方法新建一个对象。obj = desc.isInstantiable() ? desc.newInstance() : null;} catch (Exception ex) {throw (IOException) new InvalidClassException(desc.forClass().getName(),"unable to create instance").initCause(ex);}return obj;}

到目前为止,也就可以解释,为什么序列化可以破坏单例了?
序列化会通过反射调用无参数的构造方法创建一个新的对象。

我们是如何解决的呢?
只要在Singleton类中定义readResolve就可以解决该问题

//只要在Singleton类中定义readResolve就可以解决该问题
private Object readResolve() {return singleton;
}

实现原理

if (obj != null &&handles.lookupException(passHandle) == null &&desc.hasReadResolveMethod()){Object rep = desc.invokeReadResolve(obj);if (unshared && rep.getClass().isArray()) {rep = cloneArray(rep);}if (rep != obj) {handles.setObject(passHandle, obj = rep);}}

hasReadResolveMethod:如果实现了serializable 接口的类中包含readResolve则返回true

invokeReadResolve:通过反射的方式调用要被反序列化的类的readResolve方法。

总结: Singleton中定义readResolve方法,并在该方法中指定要返回的对象的生成策略,就可以防止单例被破坏。

枚举(推荐方式)

枚举单例方式是<<Effective Java>>作者推荐的使用这种方式。

在使用枚举时,构造方法会被自动调用,利用这一特性也可以实现单例;默认枚举实例的创建是线程安全的,即使反序列化也不会生成新的实例,任何情况下都是一个单例(暴力反射对枚举方式无效)。

特点: 满足单例模式所需的 创建单例、线程安全、实现简洁的需求

public enum Singleton_06{INSTANCE;private Object data;public Object getData() {return data;}public void setData(Object data) {this.data = data;}public static Singleton_06 getInstance(){return INSTANCE;}
}

问题1: 为什么枚举类可以阻止反射的破坏?

  1. 首先枚举类中是没有空参构造方法的,只有一个带两个参数的构造方法.
    在这里插入图片描述

  2. 真正原因是: 反射方法中不予许使用反射创建枚举对象

    异常: 不能使用反射方式创建enum对象

在这里插入图片描述

问题2: 为什么枚举类可以阻止序列化的破坏?
Java规范字规定,每个枚举类型及其定义的枚举变量在JVM中都是唯一的,因此在枚举类型的序列化和反序列化上,Java做了特殊的规定。

在序列化的时候Java仅仅是将枚举对象的name属性输到结果中,反序列化的时候则是通过java.lang.Enum的valueOf()方法来根据名字查找枚举对象。

比如说,序列化的时候只将INSTANCE这个名称输出,反序列化的时候再通过这个名称,查找对应的枚举类型,因此反序列化后的实例也会和之前被序列化的对象实例相同。

public enum Singleton_06{INSTANCE;
}

单例模式总结

1 ) 单例的定义
单例设计模式保证某个类在运行期间,只有一个实例对外提供服务,而这个类被称为单例类。

2 ) 单例的实现

饿汉式

  • 饿汉式的实现方式,在类加载的期间,就已经将 instance 静态实例初始化好了,所以,instance 实例的创建是线程安全的。不过,这样的实现方式不支持延迟加载实例。

懒汉式

  • 相对于饿汉式的优势是支持延迟加载。这种实现方式会导致频繁加锁、释放锁,以及并发度低等问题,频繁的调用会产生性能瓶颈。

双重检测

  • 双重检测实现方式既支持延迟加载、又支持高并发的单例实现方式。只要 instance 被创建之后,再调用 getInstance() 函数都不会进入到加锁逻辑中。所以,这种实现方式解决了懒汉式并发度低的问题。

静态内部类

  • 利用 Java 的静态内部类来实现单例。这种实现方式,既支持延迟加载,也支持高并发,实现起来也比双重检测简单。

枚举方式

  • 最简单的实现方式,基于枚举类型的单例实现。这种实现方式通过 Java 枚举类型本身的特性,保证了实例创建的线程安全性和实例的唯一性(同时阻止了反射和序列化对单例的破坏)。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词