单例设计模式之readResolve()方法

本文以懒汉单例设计模式为例,详细解析如何通过添加readResolve()方法来防止反序列化过程破坏单例模式。内容包括readObject()方法的分析,展示了从对象输入流调用到最终返回单例对象的完整流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

以懒汉单例设计模式为例,再类中添加 readResolve() 方法可防止通过反序列化破坏单例。

懒汉单例设计模式案例:

package com.singleton;

import java.io.ObjectStreamException;
import java.io.Serializable;

/**
 * 懒汉单例设计模式案例
 */
public class SingletonDemo implements Serializable {
    
	/**
	 * 懒加载
	 */
	private static SingletonDemo instance;

    /**
     * 私有构造器
     */
	private SingletonDemo() {
		// 防止通过反射实例对象而跳过getInstance()方法
		if (instance != null) {
			throw new RuntimeException("Object has been Instance !!!");
		}
	}

	/**
	 * 调用方法才加载类,资源利用率高了,但要保证线程安全
	 */
	public static synchronized SingletonDemo getInstance() {
		if (instance == null) {
			instance = new SingletonDemo();
		}
		return instance;
	}

	/**
	 * 提供readResolve()方法
	 * 当JVM反序列化恢复一个新对象时,系统会自动调用readResolve()方法返回指定好的对象
	 * 从而保证系统通过反序列化机制不会产生多的Java对象
	 * 
	 * @return 单例对象
	 * @throws ObjectStreamException 异常
	 */
	private Object readResolve() throws ObjectStreamException {
		return instance;
	}
	
}

测试用例:

package com.singleton;

import org.junit.Test;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

/**
 * 单例测试类
 */
public class SingletonTest {

	/**
	 * 测试方法
	 */
	@Test
	public void test() throws Exception {
		// 获取instance对象
		SingletonDemo instance = SingletonDemo.getInstance();

		// 获取文件输出流
		FileOutputStream fileOutputStream = new FileOutputStream("E:\\Test.txt");
		// 获取对象输出流
		ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);

		// 输出对象
		objectOutputStream.writeObject(instance);

		// 关闭资源
		objectOutputStream.close();
		fileOutputStream.close();

		// 获取对象输入流
		ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("E:\\Test.txt"));

		// 读取对象
		Object object = objectInputStream.readObject();

		// 判断两个对象是否相等,返回true/false
		System.out.println(instance == object);
	}

}

测试用例方法输出结果:

测试用例方法输出结果

readObject()方法分析:

1、对象输入流调用readObject()方法

Object object = objectInputStream.readObject();

2、readObject()方法源代码

public final Object readObject() throws IOException, ClassNotFoundException {
	// 创建对象时调用了readObject0(false)方法
	Object obj = readObject0(false);
}

3、readObject0(false)方法源代码

/**
 * 底层readObject()方法实现
 */
private Object readObject0(boolean unshared) throws IOException {
    case TC_OBJECT:
    // 可以看到这里调用了readOrdinaryObject(unshared)方法
    	return checkResolve(readOrdinaryObject(unshared));
}

4、readOrdinaryObject(unshared)方法源代码

private Object readOrdinaryObject(boolean unshared) throws IOException {
    /* desc可理解为单例类的class类,但它和JVM加载到内存中的单例class类有不同,
    	因为如果desc就是我们的单例class类,那是不允许再实例化的。
		isInstantiable()方法判断该类是否能被实例化,这里obj进行了第一次实例化*/
	obj = desc.isInstantiable() ? desc.newInstance() : null;
}

5、isInstantiable()方法源代码

/**
 * 如果该类是可序列化类或外部类,返回true
 * 要求外部类必须有一个公共的无参构造方法,可序列化类的第一个不可序列化父类定义一个可访问的无参构造方法,否则返回false
 */
boolean isInstantiable() {
    requireInitialized();
    return (cons != null);
}

6、obj实例化后继续往下执行

if (obj != null &&
    handles.lookupException(passHandle) == null &&
    // 执行hasReadResolveMethod()方法,该方法判断readResolveMethod参数是否必须有值
    desc.hasReadResolveMethod()) {

7、hasReadResolveMethod()方法源代码

boolean hasReadResolveMethod() {
    requireInitialized();
    return (readResolveMethod != null);
}

8、readResolveMethod属性通过反射找到无参的readResolve()方法赋值,我们再单例类中重新定义了readResolve()方法,也就是说会执行我们定义的readResolve()方法

readResolveMethod = getInheritableMethod(cl, "readResolve", null, Object.class);

9、接下来desc通过反射调用readResolve()方法创建对象rep

Object rep = desc.invokeReadResolve(obj);

10、invokeReadResolve(obj)方法源代码

Object invokeReadResolve(Object obj) throws IOException, UnsupportedOperationException {
	// readResolveMethod属性执行readResolve()方法
	return readResolveMethod.invoke(obj, (Object[]) null);
}

11、最后与我们在单例类中定义的一样,返回一个已实例化的instance单例对象,随后判断rep和obj是否相等,不等则将rep的值赋值给obj

if (rep != obj) {
    if (rep != null) {
        if (rep.getClass().isArray()) {
            filterCheck(rep.getClass(), Array.getLength(rep));
        } else {
            filterCheck(rep.getClass(), -1);
        }
    }
    handles.setObject(passHandle, obj = rep);
}
}
// 到此readObject()方法返回了我们需要的单例对象instance
return obj;

整个过程中我们实例化单例类obj,obj通过反射调用自身的 readResolve() 方法[也就是我们自己定义的方法]获取instance对象。也就是说我们每获取一次单例对象instance前都需要先实例化单例类,获取单例类对象obj,通过obj才能获取已实例化的单例类对象instance。

流程总结:

1.我们在单例类中定义一个readResolve()方法,用于返回instance对象。
2.反序列化获取单例类对象时调用readObject()方法。
3.readObject()方法中调用readObject0()方法。
4.readObject0()方法中调用readOrdinaryObject(boolean unshared)方法。
5.readOrdinaryObject(boolean unshared)方法中获取单例类的ObjectStreamClass对象desc,判断对象是否能实例化。可以则进行实例化,至此单例类进行了第一次实例化,对象名为obj。
6.第一次实例化完成后,通过反射寻找该单例类中的readResolve()方法,没有则直接返回obj对象。这就是我们对没有readResolve()方法的类进行序列化后生成不同对象的原因。
7.因为我们有定义readResolve()方法,desc通过invokeReadResolve(Object obj)方法调用readResolve()方法获取单例对象instance,将他赋值给rep。
8.rep与obj进行比较,由于obj是反射获取的对象,当然与rep不等,于是将rep的值instance赋值给obj,将obj返回,返回对象instance也就保证了单例。
9.简而言之就是当我们通过反序列化readObject()方法获取对象时会去寻找readResolve()方法,如果该方法不存在则直接返回新对象,如果该方法存在则按该方法的内容返回对象。

往期精彩文章:
Exception和Error的区别
MySQL基本架构与一条SQL语句的执行流程

更多分享,可关注公众号「 桂圆金宝宝 」。

桂圆金宝宝

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值