说到这个话题,我先抛出单例的饿汉式写法
单例:饿汉式
public class HungrySingleton {
private HungrySingleton() {
}
private static final HungrySingleton hungry = new HungrySingleton();
public static HungrySingleton getInstance() {
return hungry;
}
}
首先需让HungrySingleton支持序列化, 修改HungrySingleton类
public class HungrySingleton implements Serializable {
写一个 测试类对该饿汉式进行序列化、反序列化
public class Client {
public static void main(String[] args) {
HungrySingleton s1 = HungrySingleton.getInstance();
HungrySingleton s2 = null;
try {
// 将s1序列化到磁盘
FileOutputStream fos = new FileOutputStream("a.obj");
@Cleanup
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(s1);
oos.flush();
@Cleanup
FileInputStream fis = new FileInputStream("a.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
// 从磁盘反序列化
s2 = (HungrySingleton) ois.readObject();
System.out.println(s1 == s2);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
运行结果
从运行结果可以看出,序列化破坏了单例,产生了多个实例。
那我们如何解决呢?
在HungrySingleton 类中添加 readResolve()方法就可以完美解决
public class HungrySingleton implements Serializable {
private HungrySingleton() {
}
private static final HungrySingleton hungry = new HungrySingleton();
public static HungrySingleton getInstance() {
return hungry;
}
// 我们添加的readResolve()方法
private Object readResolve() {
return hungry;
}
}
如上添加后,运行Client我们对测试类,可以看到打印出true。
大家一定会有疑问,readResolve这个方法为啥可以解决序列化破坏单例的问题
readResolve()为啥就可以解决序列化破坏单例的问题呢?
源头就在于我们测试类中的
ObjectInputStream ois = new ObjectInputStream(fis);
singleton1 = (HungrySingleton) ois.readObject();// 这句代码是我们的入手点
进到ObjectInputStream#readObject()看源码,try第一句代码就是
//返回的obj对象,就是ObjectInputStream的readObject0返回的对象。
Object obj = readObject0(false);
进入ObjectInputStream#readObject0(),switch语句对枚举或者Object类都有对应的序列化机制
重点代码
case TC_ENUM:
// 这句代码是针对枚举,单例中为啥枚举式最安全,就是看这行代码,后续,小伙伴可以研读研读
return checkResolve(readEnum(unshared));
case TC_OBJECT:
//我们的Object 类
return checkResolve(readOrdinaryObject(unshared));
checkResolve:检查对象,并替换
readOrdinaryObject:读取二进制对象
我们先进入readOrdinaryObject()方法
try {
obj = desc.isInstantiable() ? desc.newInstance() : null;
} catch (Exception ex) {
throw (IOException) new InvalidClassException(
desc.forClass().getName(),
"unable to create instance").initCause(ex);
}
可以看到,readOrdinaryObject()方法是通过desc.isInstantiable() 来判断是否需要new一个对象,如果返回true,方法通过反射的方式调用无参构造方法新建一个对象,否则,返回空。
那我们进入isInstantiable()方法,
boolean isInstantiable() {
requireInitialized();
//cons是构造函数
return (cons != null);
}
cons != null是判断类的构造方法是否为空,我们大家应该知道,Class类的构造方法肯定不为空,显然isInstantiable()返回true,也就是说,一定会new 一个对象,且被obj接收。
我们回到readOrdinaryObject()方法,查看初始化完成后的操作。
if (obj != null &&
handles.lookupException(passHandle) == null &&
desc.hasReadResolveMethod())
{
// !敲重点,这句代码块是为什么会执行我们在单例中定义的readResolve()方法的核心。
Object rep = desc.invokeReadResolve(obj);
if (unshared && rep.getClass().isArray()) {
rep = cloneArray(rep);
}
if (rep != obj) {
// Filter the replacement object
if (rep != null) {
if (rep.getClass().isArray()) {
filterCheck(rep.getClass(), Array.getLength(rep));
} else {
filterCheck(rep.getClass(), -1);
}
}
handles.setObject(passHandle, obj = rep);
}
}
我们要敲黑板了,这里就是单例类中定义readResolve就可以解决问题的关键所在!
若"obj != null &&handles.lookupException(passHandle) == null &&desc.hasReadResolveMethod()"这条语句返回true
就会调用Object rep = desc.invokeReadResolve(obj) 这条语句。
我们进入hasReadResolveMethod()方法
boolean hasReadResolveMethod() {
requireInitialized();
return (readResolveMethod != null);
}
"readResolveMethod != null "的判断,我们深究进去,readResolveMethod是从哪里读取进来的
/** class-defined readResolve method, or null if none */
// 定义readResolveMethod 的方法
private Method readResolveMethod;
// 对readResolveMethod赋值, 通过反射获得类中名为readResolve的方法
readResolveMethod = getInheritableMethod(
cl, "readResolve", null, Object.class);
也就是说!
若目标类有readResolve方法,那就通过反射的方式调用要被反序列化的类中的readResolve方法,返回一个对象,然后把这个新的对象复制给之前创建的obj(即最终返回的对象)。那被反序列化的类中的readResolve 方法里是什么?就是直接返回我们的单例对象。
再次贴上来,我们的单例类。
public class HungrySingleton implements Serializable {
private HungrySingleton() {
}
private static final HungrySingleton hungry = new HungrySingleton();
public static HungrySingleton getInstance() {
return hungry;
}
private Object readResolve() {
return hungry;
}
}
嘻嘻,
如有不对之处还希望各位留言指正,以免误导他人。