前言
上篇文章我们讲述了,单例的推荐使用方式,以及反射对单例的破坏,但文末留了一个疑问,就是序列化如何破坏一个单例,那么本篇文章就来分析一下。
序列化对于单例的破坏
Singleton.class
public 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;
}
}
public class SerializableTest {
public static void main(String[] args) throws Exception{
// 序列化对象输出流
ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("file.temp")
);
oos.writeObject(Singleton.getInstance());
// 序列化对象输入流
File file = new File("file.temp");
ObjectInputStream ois = new ObjectInputStream(
new FileInputStream(file)
);
Singleton singleton = (Singleton) ois.readObject();
// false
// 通过对Singleton的序列化与反序列化得到的对象是一个新的对象,这就破坏了Singleton的单例性。
System.out.println(Singleton.getInstance() == singleton);
}
}
为什么对象不一样?
其实问题是出在这个ois.readObject();这个读取逻辑中, 它的源码如下:
public final Object readObject() throws IOException, ClassNotFoundException {
return readObject(Object.class);
}
private final Object readObject(Class<?> type) throws IOException, ClassNotFoundException {
if (enableOverride) {
return readObjectOverride();
}
if (! (type == Object.class || type == String.class))
throw new AssertionError("internal error");
// if nested read, passHandle contains handle of enclosing object
int outerHandle = passHandle;
try {
// 最终会返回一个object对象,这一步要做的就是反序列化对象
Object obj = readObject0(type, false);
handles.markDependency(outerHandle, passHandle);
ClassNotFoundException ex = handles.lookupException(passHandle);
if (ex != null) {
throw ex;
}
if (depth == 0) {
vlist.doCallbacks();
}
return obj;
} finally {
passHandle = outerHandle;
if (closed && depth == 0) {
clear();
}
}
}
/**
* Underlying readObject implementation.
* @param type a type expected to be deserialized; non-null
* @param unshared true if the object can not be a reference to a shared object, otherwise false
*/
private Object readObject0(Class<?> type, boolean unshared) throws IOException {
boolean oldMode = bin.getBlockDataMode();
if (oldMode) {
int remain = bin.currentBlockRemaining();
if (remain > 0) {
throw new OptionalDataException(remain);
} else if (defaultDataEnd) {
/*
* Fix for 4360508: stream is currently at the end of a field
* value block written via default serialization; since there
* is no terminating TC_ENDBLOCKDATA tag, simulate
* end-of-custom-data behavior explicitly.
*/
throw new OptionalDataException(true);
}
bin.setBlockDataMode(false);
}
byte tc;
while ((tc = bin.peekByte()) == TC_RESET) {
bin.readByte();
handleReset();
}
depth++;
totalObjectRefs++;
try {
switch (tc) {
case TC_NULL:
return readNull();
case TC_REFERENCE:
// check the type of the existing object
return type.cast(readHandle(unshared));
case TC_CLASS:
if (type == String.class) {
throw new ClassCastException("Cannot cast a class to java.lang.String");
}
return readClass(unshared);
case TC_CLASSDESC:
case TC_PROXYCLASSDESC:
if (type == String.class) {
throw new ClassCastException("Cannot cast a class to java.lang.String");
}
return readClassDesc(unshared);
case TC_STRING:
case TC_LONGSTRING:
return checkResolve(readString(unshared));
case TC_ARRAY:
if (type == String.class) {
throw new ClassCastException("Cannot cast an array to java.lang.String");
}
return checkResolve(readArray(unshared));
case TC_ENUM:
if (type == String.class) {
throw new ClassCastException("Cannot cast an enum to java.lang.String");
}
return checkResolve(readEnum(unshared));
case TC_OBJECT:
if (type == String.class) {
throw new ClassCastException("Cannot cast an object to java.lang.String");
}
return checkResolve(readOrdinaryObject(unshared));
case TC_EXCEPTION:
if (type == String.class) {
throw new ClassCastException("Cannot cast an exception to java.lang.String");
}
IOException ex = readFatalException();
throw new WriteAbortedException("writing aborted", ex);
case TC_BLOCKDATA:
case TC_BLOCKDATALONG:
if (oldMode) {
bin.setBlockDataMode(true);
bin.peek(); // force header read
throw new OptionalDataException(
bin.currentBlockRemaining());
} else {
throw new StreamCorruptedException(
"unexpected block data");
}
case TC_ENDBLOCKDATA:
if (oldMode) {
throw new OptionalDataException(true);
} else {
throw new StreamCorruptedException(
"unexpected end of block data");
}
default:
throw new StreamCorruptedException(
String.format("invalid type code: %02X", tc));
}
} finally {
depth--;
bin.setBlockDataMode(oldMode);
}
}
上面的代码可能看起来比较晦涩,因为这其中还涉及了序列化的一些机制,这个我们有机会可以拿几篇文章来分析一下这段源码(但是如果你感兴趣,欢迎联系我,探讨一下),能明确说明的是,最终它会执行如下方法:
// 匹配的是对象
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;
}
到这个方法里面,你应该就不陌生了,所以结论就是它会通过反射调用无参数的构造方法创建一个新的对象。
如何解决?
解决这个问题其实很简单,在Java序列化机制中,提供了readResolve方法,我们在深拷贝的时候也会经常遇到,我们只需要利用此方法,就能解决此类问题,如下:
public 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;
}
/**
* 解决方案:只要在Singleton类中定义readResolve就可以解决该问题
* 程序会判断是否有readResolve方法,如果存在就在执行该方法,如果不存在--就创建一个对象
*/
private Object readResolve() {
return singleton;
}
}
ReadResolve实现原理
在上文所述的readOrdinaryObject方法中,会有如下逻辑:
if (obj != null &&
handles.lookupException(passHandle) == null &&
// 如果实现了serializable接口的类中,并且包含readResolve方法则返回true
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);
}
}
// 替换obj
handles.setObject(passHandle, obj = rep);
}
}
readResolveMethod = getInheritableMethod(
cl, "readResolve", null, Object.class);
Object invokeReadResolve(Object obj)
throws IOException, UnsupportedOperationException {
requireInitialized();
if (readResolveMethod != null) {
try {
return readResolveMethod.invoke(obj, (Object[]) null);
} catch (InvocationTargetException ex) {
Throwable th = ex.getTargetException();
if (th instanceof ObjectStreamException) {
throw (ObjectStreamException) th;
} else {
throwMiscException(th);
throw new InternalError(th); // never reached
}
} catch (IllegalAccessException ex) {
// should not occur, as access checks have been suppressed
throw new InternalError(ex);
}
} else {
throw new UnsupportedOperationException();
}
}