越来越成熟了,那是不是坚不可摧的呢,现在我们就要用序列号和反序列化来破坏单例模式,后面也会重点讲一下原理,
好好听,让我们来一起破坏单例模式吧,首先还是来到Test类里边
package com.learn.design.pattern.creational.singleton;
import java.io.Serializable;
/**
* 我们让他来实现序列号接口
* 具体序列话和反序列化的版本号呢
* 我们就不关注了
* 因为这个不是重点
*
* 我们现在的HungrySingleton实现了Serializable接口
* 那么在ObjectStreamClass这里边
* 判断就会返回true
* boolean isInstantiable() {
* requireInitialized();
* return (cons != null);
* }
* 返回true之后再回来
*
* @author Leon.Sun
*
*/
public class HungrySingleton implements Serializable,Cloneable{
private final static HungrySingleton hungrySingleton;
static{
hungrySingleton = new HungrySingleton();
}
private HungrySingleton(){
if(hungrySingleton != null){
throw new RuntimeException("单例构造器禁止反射调用");
}
}
public static HungrySingleton getInstance(){
return hungrySingleton;
}
/**
* 我们写一个方法
* 返回一个Object
*
* 从override里面根本就没有
* 他根本就不是Object的方法
* 那方法为什么又叫readResolve呢
* 叫别的名字可不可以呢
* 那我们就较真
* 为什么要这么写
*
*
* @return
*/
private Object readResolve(){
/**
* 然后在这里面返回一个单例对象
* 我们回到Test里边
* 我们直接再run一下
* 咱们一起来看
* 最后看是否相等呢
* 最后返回true
* 这就很神奇了
* 回到这个单例类里面
* 你们肯定有一个疑问
* 你为什么写这个方法啊
*
*
*
*/
return hungrySingleton;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return getInstance();
}
}
package com.learn.design.pattern.creational.singleton;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InvalidClassException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamClass;
import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* 我们已经确定单例模式只能获取一个对象
* 就使用HungrySingleton这个类作为测试
*
*
* @author Leon.Sun
*
*/
public class Test {
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
// LazySingleton lazySingleton = LazySingleton.getInstance();
// System.out.println("main thread"+ThreadLocalInstance.getInstance());
// System.out.println("main thread"+ThreadLocalInstance.getInstance());
// System.out.println("main thread"+ThreadLocalInstance.getInstance());
// System.out.println("main thread"+ThreadLocalInstance.getInstance());
// System.out.println("main thread"+ThreadLocalInstance.getInstance());
// System.out.println("main thread"+ThreadLocalInstance.getInstance());
// Thread t1 = new Thread(new T());
// Thread t2 = new Thread(new T());
// t1.start();
// t2.start();
// System.out.println("program end");
/**
* 从这里面拿了一个对象来
* 那这个单例对象我们拿到了
* 现在我们想象一下
* 如果我们把这个instance序列号到一个文件中
* 然后再从文件里取出来
* 那这两个对象还是同一个对象吗
* 那现在我们就来测试一下
*
* 首先单例获取一个对象
*
*
*/
HungrySingleton instance = HungrySingleton.getInstance();
// EnumInstance instance = EnumInstance.getInstance();
// instance.setData(new Object());
//
/**
* 我们直接用ObjectOutputStream流
* oos就是ObjectOutputStream三个字母的首字母小写
* new一个ObjectOutputStream
* 里面new一个FileOutputStream
* 名字我们起一个名字就叫singleton_file
* 这个时候这里爆红了
* 应该是异常
* 我们也不try catch了
* 我们直接抛出
* 然后呢继续
*
* 看一下我们使用的类ObjectOutputStream
*
* 然后序列号
*
*/
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton_file"));
/**
* 我们通过ObjectOutputStream对象写一下
*
*
*/
oos.writeObject(instance);
//
/**
* 现在我们再读一下他
* new一个File
* 文件名singleton_file
*
*
*/
File file = new File("singleton_file");
/**
* 我们再通过ObjectInputStream读取这个文件
* 里面再放一个FileInputStream
* 把这个file放进来
* 这个时候我们通过ObjectInputStream获取的这个对象去读取他
* 和上面声明的instance做一个对比
* 判断他们是不是同一个对象
*
* ObjectInputStream
* 我们重点看一下ois这个对象
* 也就是ObjectInputStream这个类的对象
* 他的readObject方法
* 我们进来看一下
* 看一下这个方法
* 从上往下看
* 重点看一下Object obj = readObject0(false);这一行的代码
* 他又调用readObject0这个方法
* 我们进来看一下
* private Object readObject0(boolean unshared)
* 这个就是读取对象
* 各种逻辑不是我们要讲的重点
* 我们往下看
* 这里面有一个switch
* switch (tc)
* 读对应的类型
* TC_STRING
* TC_LONGSTRING
* TC_ARRAY
* TC_ENUM
* TC_OBJECT
* 那我们的是什么呢
* 肯定是Object
* TC_OBJECT
* 所以我们的代码会走到return checkResolve(readOrdinaryObject(unshared));这一行
* 然后我们看一下他调的方法
* 这一行调了两个方法
* 首先调了readOrdinaryObject这个方法
* 然后把返回值放入checkResolve方法
* 我们先进入readOrdinaryObject这个方法
* private Object readOrdinaryObject(boolean unshared)
* 往下看
* 上面还是各种判断校验
* 这里可以看到
* if (cl == String.class || cl == Class.class
* || cl == ObjectStreamClass.class) {
* throw new InvalidClassException("invalid class descriptor");
* }
* 如果是String,
* Class或者ObjectClass
* 就会报一个异常
* 无效的class
* Object obj;
* 然后我们看一下obj = desc.isInstantiable() ? desc.newInstance() : null;这一行
* 这里面做了一个判断
* 首先我们看一下obj这个Object对象
* 然后往下走
* 看看他做什么用
* return obj;
* 我们看到把它返回回去了
* 接着回来
* 那我们就看这一行
* obj = desc.isInstantiable() ? desc.newInstance() : null
* 这里面做了一个判断
* 如果isInstantiable这个返回的是true的话
* 就会生成一个新的对象
* 否则返回null
* 这个就是从readObject传进来的
* 所以我们看一下
* isInstantiable()这个方法是做什么用的
* 这里呢很简单
* boolean isInstantiable() {
* requireInitialized();
* return (cons != null);
* }
* return cons不等于空
* cons是什么呢
* private Constructor<?> cons;
* 也就是反射的构造器类型
* public final class Constructor<T> extends Executable
* 回来
* 从这行代码看不出什么
* 那这个时候我们就得看注释了
* 还好我们有注释可以看
* 这个注释意思是说
* Returns true if represented class is serializable/externalizable and can
* 我们看一下serializable/externalizable
* 包括下面还有be instantiated by the serialization runtime--i.e., if it is
* 简单的说呢
* 如果serializable或者externalizable
* 如果这样的一个类class
* 正在运行时被实例化
* 那么该方法就会返回true
* 所以returns true
* 那这两个序列化方式
* serializable这个是比较全的
* 而externalizable这个是定制哪些字段
* 都可以通过它来定制
* 那么看一下这个类
* 他又继承serializable
* 他作为接口又是serializable的子类
* 而我们平时实现的都是serializable这个类
* 那平时定制序列号的情况
* 也比较少见
* 那就好理解了
* 我们现在的HungrySingleton实现了Serializable接口
* 那么在ObjectStreamClass这里边
* 判断就会返回true
* boolean isInstantiable() {
* requireInitialized();
* return (cons != null);
* }
* 返回true之后再回来
* obj = desc.isInstantiable() ? desc.newInstance() : null;
* isInstantiable他为true的话就会newInstance
* 然后把obj返回回去
* 到这儿就比较清晰了
* 这个对象是通过反射创建出来的对象
* newInstance
* ObjectStreamClass desc = readClassDesc(false);
* desc是ObjectStreamClass类型的
* 既然通过反射去创建对象
* 那肯定和之前的对象不是同一个
* 这也就是解释了我刚刚为什么序列化和反序列化单例模式破坏了
* 那到这里之后啊
* 还是没有找到我们想要的答案
* 我们接着往下看
* if (obj != null &&
* handles.lookupException(passHandle) == null &&
* desc.hasReadResolveMethod())
* 判断obj不会为空
* 然后进行一系列的判断
* 然后深入进去看了
* 然后看看hasReadResolveMethod这个
* 从这个名字就能够看出来
* 判断它是否有ReadResolve方法
* 我们进来看一下
* boolean hasReadResolveMethod() {
* requireInitialized();
* return (readResolveMethod != null);
* }
* 这个也很简单
* 我们首先看一下这个对象
* 他就是一个Method
* private Method readResolveMethod;
* return (readResolveMethod != null);
* 也就是这一行通过源码看不出什么
* 那我们就看注释
* 注释很有用
* 平时看源码的时候一定要养成看注释的好习惯
* Returns true if represented class is serializable or externalizable and
* 返回true
* 如果这个类是serializable这个接口或者externalizable这个接口类型
* 实现了这个接口那class自然也是这个类型
* and还有一个条件
* defines a conformant readResolve method. Otherwise, returns false.
* 那这个注释已经说得很清楚了
* 我们回来
* 因为哦我们定义了这个方法
* 所以desc.hasReadResolveMethod()这里判断剩下
* 那怎么调用那个方法呢
* Object rep = desc.invokeReadResolve(obj);
* 把obj传进来
* 进来看一下
* 很明显通过这个名字就能够看出来
* 它是通过反射来调用invokeReadResolve方法的
* return readResolveMethod.invoke(obj, (Object[]) null);
* 也就是说这个时候调用HungrySingleton里面写的readResolve方法
* 那么再回到ObjectStreamClass里边
* return readResolveMethod.invoke(obj, (Object[]) null);
* 这个就是反射方法
* 那这个方法名在哪里定义的呢
* 抱着这个好奇心搜索这个关键字
* readResolveMethod = getInheritableMethod(
* cl, "readResolve", null, Object.class);
* readObjectMethod赋值成什么呢
* 名字赋成readResolve
* readResolve他就是我们刚刚写的目标方法名
* 就在这里
* 终于找到他了
* 具体这个源码怎么看呢
* 我会debug根据源码里面一行一行看一下
* 你们也可以去看源码
* 这个是学习框架和源码非常好的一种方式
* 那回来
* 所以这个方法是通过反射出来的
* 也没什么继承关系
* 所以只能把这个方法直接写到这里
* 一起来debug一下
* 看源码的时候也要看关键点
* 现在一起debug一下
* 我们跟一遍源码
*
* 反序列化出来
*
*/
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
//
/**
* 强转把异常抛出
*
* 这个时候可以调用readObject方法
* 然后进入readObject0
* 我们再打一个断点
* 之后就来到这里了
* 我们看一下我们刚刚关注的Object这个case
* case TC_OBJECT:
* 看到进入到return checkResolve(readOrdinaryObject(unshared));
* 然后readOrdinaryObject这个方法
* 下边继续
* obj = desc.isInstantiable() ? desc.newInstance() : null;
* 我们看一下desc.isInstantiable()他的返回值是什么
* 正如我们的预期他的返回值是true
* 所以他会调一个newInstance
* 我们单步走一下
* 这个时候我们在内存里面看一下obj
* Object obj;
* 我们看到这个对象其实已经new出来了
* HungrySingleton
* 后面我记得还有一个断点F8
* desc.hasReadResolveMethod()
* 判断方法有没有
* 看来进入了if
* Object rep = desc.invokeReadResolve(obj);
* 开始invoke这个方法了
* 进来
* 注意if (readResolveMethod != null)这个
* F8过来
* 判断他是不是空
* 我们打开看一下
* 这个Method类型
* 可以看到name是readResolve
* 他并不是空
* 所以F6单步
* return readResolveMethod.invoke(obj, (Object[]) null);
* 这个时候反射调用这个方法
* 会到HungrySingleton类里边
* private Object readResolve()
* 这个时候就有了
* F8这个时候
* return hungrySingleton;
* 这个时候就调用了这个方法
* 而hungrySingleton这个对象我们看一下
* 他本身已经创建好了
* 是420
* 而这个时候
* return的时候
* 就把这个对象返回回去了
* 而不会刚刚通过反射newInstance
* 这个时候return obj;
* 返回的是hungrySingleton 420
* 也就是HungrySingleton这里面的对象
* F8继续
* 下边我们就不看了
* 刚刚讲的这一串呢
* 是序列号和反序列化的一个重点
* 核心
* 现在F8直接过
* 回到Test里面看console一模一样
* 可是有一点
* 我们考虑一下
* 虽然最终返回的是同一个对象了
* 但是在这个过程中
* 其实它实例化对象了
* 只是最后返回没有返回而已
* 所以一旦我们的业务场景涉及序列化和反序列化的时候
* 一定要注意对单例的破坏
* 这个呢非常重要
* 希望通过这个能对序列化和反序列化单例的破坏有一个深入的理解
* 并且学会如何看源码
* 如何找源码的关键路径
* 这些技能对成长是非常有益处的
* 我们接下来看一下对于反射这种情况
* 我们如何来防御呢
*
*
*
*/
HungrySingleton newInstance = (HungrySingleton) ois.readObject();
// EnumInstance newInstance = (EnumInstance) ois.readObject();
//
// System.out.println(instance.getData());
// System.out.println(newInstance.getData());
// System.out.println(instance.getData() == newInstance.getData());
// Class objectClass = HungrySingleton.class;
// Class objectClass = StaticInnerClassSingleton.class;
// Class objectClass = LazySingleton.class;
// Class objectClass = EnumInstance.class;
// Constructor constructor = objectClass.getDeclaredConstructor(String.class,int.class);
//
// constructor.setAccessible(true);
// EnumInstance instance = (EnumInstance) constructor.newInstance("Geely",666);
//
// LazySingleton newInstance = (LazySingleton) constructor.newInstance();
// LazySingleton instance = LazySingleton.getInstance();
// StaticInnerClassSingleton instance = StaticInnerClassSingleton.getInstance();
// StaticInnerClassSingleton newInstance = (StaticInnerClassSingleton) constructor.newInstance();
// HungrySingleton newInstance = (HungrySingleton) constructor.newInstance();
// HungrySingleton instance = HungrySingleton.getInstance();
/**
* 在判断之前呢
* 我们把这个两个对象都输出一下
* 我们run一下
* 这里面会抛一个异常
* 这个异常我们看到就知道了
* 这里的异常说了
* 不是可序列化的异常
* 很简单
* 我们让他来实现序列号接口
* 刚刚这个异常是特意留出来的
* 希望印象深刻
* instance是001
* newInstance002
* 他们是不相等
* 那目前来看就违背了单例模式的一个初衷
* 通过序列化和反序列化拿到了不同的对象
* 而我们只希望拿到同一个对象
* 那这个事情要怎么解呢
* 那解这个问题也不难
* 重要的是理解
* 理解他的原理是什么
* 我们这里只是以饿汉式来做例子
* 其他方式可以自己尝试一下
* 异曲同工
* 那么来到这个单例类里面
*
*
*
*/
System.out.println(instance);
System.out.println(newInstance);
/**
* instance和newInstance做一个对比
*
*/
System.out.println(instance == newInstance);
// EnumInstance instance = EnumInstance.getInstance();
// instance.printTest();
}
}
public final Object readObject()
throws IOException, ClassNotFoundException
{
if (enableOverride) {
return readObjectOverride();
}
// if nested read, passHandle contains handle of enclosing object
int outerHandle = passHandle;
try {
Object obj = readObject0(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();
}
}
}
private Object readObject0(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:
return readHandle(unshared);
case TC_CLASS:
return readClass(unshared);
case TC_CLASSDESC:
case TC_PROXYCLASSDESC:
return readClassDesc(unshared);
case TC_STRING:
case TC_LONGSTRING:
return checkResolve(readString(unshared));
case TC_ARRAY:
return checkResolve(readArray(unshared));
case TC_ENUM:
return checkResolve(readEnum(unshared));
case TC_OBJECT:
return checkResolve(readOrdinaryObject(unshared));
case TC_EXCEPTION:
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);
}
}
private Object readOrdinaryObject(boolean unshared)
throws IOException
{
if (bin.readByte() != TC_OBJECT) {
throw new InternalError();
}
ObjectStreamClass desc = readClassDesc(false);
desc.checkDeserialize();
Class<?> cl = desc.forClass();
if (cl == String.class || cl == Class.class
|| cl == ObjectStreamClass.class) {
throw new InvalidClassException("invalid class descriptor");
}
Object obj;
try {
obj = desc.isInstantiable() ? desc.newInstance() : null;
} catch (Exception ex) {
throw (IOException) new InvalidClassException(
desc.forClass().getName(),
"unable to create instance").initCause(ex);
}
passHandle = handles.assign(unshared ? unsharedMarker : obj);
ClassNotFoundException resolveEx = desc.getResolveException();
if (resolveEx != null) {
handles.markException(passHandle, resolveEx);
}
if (desc.isExternalizable()) {
readExternalData((Externalizable) obj, desc);
} else {
readSerialData(obj, desc);
}
handles.finish(passHandle);
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) {
// 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);
}
}
return obj;
}
/**
* Returns true if represented class is serializable/externalizable and can
* be instantiated by the serialization runtime--i.e., if it is
* externalizable and defines a public no-arg constructor, or if it is
* non-externalizable and its first non-serializable superclass defines an
* accessible no-arg constructor. Otherwise, returns false.
*/
boolean isInstantiable() {
requireInitialized();
return (cons != null);
}
/**
* Returns true if represented class is serializable (but not
* externalizable) and defines a conformant writeObject method. Otherwise,
* returns false.
*/
boolean hasWriteObjectMethod() {
requireInitialized();
return (writeObjectMethod != null);
}
/**
* Invokes the readResolve method of the represented serializable class and
* returns the result. Throws UnsupportedOperationException if this class
* descriptor is not associated with a class, or if the class is
* non-serializable or does not define readResolve.
*/
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();
}
}
if (serializable) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
if (isEnum) {
suid = Long.valueOf(0);
fields = NO_FIELDS;
return null;
}
if (cl.isArray()) {
fields = NO_FIELDS;
return null;
}
suid = getDeclaredSUID(cl);
try {
fields = getSerialFields(cl);
computeFieldOffsets();
} catch (InvalidClassException e) {
serializeEx = deserializeEx =
new ExceptionInfo(e.classname, e.getMessage());
fields = NO_FIELDS;
}
if (externalizable) {
cons = getExternalizableConstructor(cl);
} else {
cons = getSerializableConstructor(cl);
writeObjectMethod = getPrivateMethod(cl, "writeObject",
new Class<?>[] { ObjectOutputStream.class },
Void.TYPE);
readObjectMethod = getPrivateMethod(cl, "readObject",
new Class<?>[] { ObjectInputStream.class },
Void.TYPE);
readObjectNoDataMethod = getPrivateMethod(
cl, "readObjectNoData", null, Void.TYPE);
hasWriteObjectData = (writeObjectMethod != null);
}
domains = getProtectionDomains(cons, cl);
writeReplaceMethod = getInheritableMethod(
cl, "writeReplace", null, Object.class);
readResolveMethod = getInheritableMethod(
cl, "readResolve", null, Object.class);
return null;
}
});
} else {
suid = Long.valueOf(0);
fields = NO_FIELDS;
}