今天读《Effective Java》这本书时,读到第77条时,困惑了很久,对里面列举单例模式反序列化问题久久不能理解,因此特意探究了下,对反序列化的流程有了进一步的认识:
1)反序列化时,是根据序列化时写入流的类路径去加载类的,采用的方法如下:
ObjectInputStream—>resolveClass(ObjectStreamClass desc){
String name = desc.getName();
try {
return Class.forName(name, false, latestUserDefinedLoader());
} catch (ClassNotFoundException ex) {
Class<?> cl = primClasses.get(name);
if (cl != null) {
return cl;
} else {
throw ex;
}
}
}
我们一般接触到了加载方式,都是class.forName(String name),这种方式默认是加载时初始化静态变量的,而反序列化加载类时是不执行初始化操作的;
2)反序列化时是不会调用类中的构造函数,这一点需要明确(java编程思想中有说到,但是看到这个地方时,没想起来,然后蒙了一段时间),至于具体怎么创建,研究了半天源码,但有一个关键的地方没有找到——Constructor类中的 constructorAccessor变量的初始化地方;通过单步调试,可以看到,反序列化调用的constructorAccessor 变量的实际内容是GeneratedSerializationConstructorAccseeor的一个实例,通过类名也能发现这个类的作用与平常类的实例化不同;然后由此产生一个相应类的实例。
因此,每次反序列化单例对象时,都是重新创建了一个实例,违背了单例模式的意义,所以需要重写readResolve()方法,返回新建对象共享的静态变量(在第一次引用时被初始化,之后就不会被改变了),保证对象引用同一个实例