首先我们来看一下序列化和反序列化是怎么破坏单例的。看代码

public class HungrySingleton implements Serializable{private final static HungrySingleton hungrySingleton;static{hungrySingleton = new HungrySingleton();}private HungrySingleton(){if(hungrySingleton != null){throw new RuntimeException("单例构造器禁止反射调用");}}public static HungrySingleton getInstance(){return hungrySingleton;}
}
复制代码

这里我们使用之前的饿汉式的单例作为例子。在之前饿汉式的代码上做点小改动。就是让我们的单例类实现 Serializable接口。然后我们在测试类中测试一下怎么破坏。

public class SingletonTest {public static void main(String[] args) throws IOException, ClassNotFoundException {HungrySingleton instance = HungrySingleton.getInstance();ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton_file"));oos.writeObject(instance);File file = new File("singleton_file");ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));HungrySingleton newInstance = (HungrySingleton) ois.readObject();System.out.println(instance == newInstance;}}
复制代码

这里首先我们使用正常的方式来获取一个对象。通过序列化将对象写入文件中,然后我们通过反序列化的到一个对象,我们再对比这个对象,输出的内存地址和布尔结果都表示这不是同一个对象。也就说我们通过使用序列化和反序列化破坏了这个单例,那我们该如何防治呢?防治起来很简单,只需要在单例类中添加一个readResolve方法,下面看代码:

public class HungrySingleton implements Serializable,Cloneable{private final static HungrySingleton hungrySingleton;static{hungrySingleton = new HungrySingleton();}public static HungrySingleton getInstance(){return hungrySingleton;}private Object readResolve(){return hungrySingleton;}
}
复制代码

此时我们再通过测试类进行测试即可发现我们通过序列化和反序列化得到的还是同一个对象。那么为什么添加一个这个方法就可以防止呢?下我们跟进去看看为什么

首先这个readResolve方法不是object里面的方法。我们进我们的测试类中去看看这行中的HungrySingleton newInstance = (HungrySingleton) ois.readObject()中的 readObject()的实现。我们只把关键代码贴出来。

    public final Object readObject()throws IOException, ClassNotFoundException{if (enableOverride) {return readObjectOverride();}// if nested read, passHandle contains handle of enclosing objectint 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;
复制代码

我们重点来看一下 Object obj = readObject0(false)这一行这里调用了一个readObject0方法,我们再深入看一下这个readObject0方法的实现。

    /*** Underlying readObject implementation.*/private Object readObject0(boolean unshared) throws IOException {....  //各种判断逻辑我们暂时不管switch (tc) {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:}....     }
复制代码

我们看这个 case TC_OBJECT: 也就是判断为object之后的代码,checkResolve(readOrdinaryObject(unshared))这行先是调用了readOrdinaryObject()方法,然后将方法的返回值返回给checkResolve方法,我们先查看一下readOrdinaryObject()方法。

     /*** Reads and returns "ordinary" (i.e., not a String, Class,* ObjectStreamClass, array, or enum constant) object, or null if object's* class is unresolvable (in which case a ClassNotFoundException will be* associated with object's handle).  Sets passHandle to object's assigned* handle.*/private Object readOrdinaryObject(boolean unshared){.....//各种判断校验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);}.....return obj;}复制代码

我们看一下 obj = desc.isInstantiable() ? desc.newInstance() : null这一行中的obj对象是干嘛用的 我们往下翻在这个方法的最后将这个obj返回出去了。我们又回头看这个这一行obj = desc.isInstantiable() ? desc.newInstance() : null 这个进行判断如果 obj==desc.isInstantiable()就返回一个新的对象,否则返回空,代码看到这里好像有点眉目,我再看看isInstantiable这个方法的实现。

 /*** 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);}复制代码

isInstantiable方法实现很简单,这里的cons是什么呢?我们继续看

 /** serialization-appropriate constructor, or null if none */private Constructor<?> cons;
复制代码

cons是构造器这里是通过反射获取的对象,光看着一行代码我们好像并不能看出啥东西,这时候我们看一下这一行代码的注释。 翻译过来的话就是:

如果表示的类是serializable/externalizable并且可以由序列化运行时实例化,则返回true - 如果它是可外部化的并且定义了公共的无参数构造函数,或者它是不可外化的,并且它的第一个非可序列化的超类定义了可访问的无参数构造函数。否则,返回false。

externalizable这个类是serializable的一个子类用于制定序列化,比如自定义某个属性的序列化,用的比较少。 好,我们的单例实现了serializable接口所以这里返回的是true,那么回到我们之前看看到的那里,也就是这里obj = desc.isInstantiable() ? desc.newInstance() : null 此时返回的就是一个newInstance是通过反射拿到的对象,既然是反射拿到的对象自然是一个新的对象,看到这里我们算弄明白了为什么序列化获取的是一个新的对象。不过到这里还是没有得到我们想要的知道的为什么写了一个readResolve方法就可以解决反序列化得到的不是同一个对象的问题,那么我们继续往下看ObjectInputSteam这个类

 if (obj != null &&handles.lookupException(passHandle) == null &&desc.hasReadResolveMethod()){Object rep = desc.invokeReadResolve(obj);if (unshared && rep.getClass().isArray()) {rep = cloneArray(rep);}
复制代码

看到这里,这里对obj进行了一次空判断,这里我们刚分析了obj不会为空,看这里desc.hasReadResolveMethod()从命名我们可以看出这个判断是判断否包含readResolve这个方法。我们再点进去看看这个的实现

    /*** Returns true if represented class is serializable or externalizable and* defines a conformant readResolve method.  Otherwise, returns false.*/boolean hasReadResolveMethod() {requireInitialized();return (readResolveMethod != null);}
复制代码

这里依旧是看代码没啥看的,我们看看注释,符合我们的猜测,也就是说这个

if (obj != null &&handles.lookupException(passHandle) == null &&desc.hasReadResolveMethod())
复制代码

判断结果为true那么我们再看看这个desc.invokeReadResolve(obj)的实现

/*** 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 suppressedthrow new InternalError(ex);}} else {throw new UnsupportedOperationException();}}
复制代码

这里我们看方法名的也能猜测这是使用了反射来调用,看这一行 return readResolveMethod.invoke(obj, (Object[]) null) 使用了反射来调用readResolveMethod方法。可是你可能会问了 也没看到用readResolveMethod这个方法啊,我对这个类进行搜索一下 readResolve

/*** Creates local class descriptor representing given class.*/private ObjectStreamClass(final Class<?> cl) {.....domains = getProtectionDomains(cons, cl);writeReplaceMethod = getInheritableMethod(cl, "writeReplace", null, Object.class);readResolveMethod = getInheritableMethod(cl, "readResolve", null, Object.class);return null;....
复制代码

在这里可以看到是获取了readResolve这个方法。这样就算解决了我们最初的疑问了。同学们可以根据我说的源码在相应的地方打断点看看。

序列化和反序列化的对单例破坏的防止及其原理相关推荐

  1. java-Transient关键字、Volatile关键字介绍和序列化、反序列化机制、单例类序列化

    - Transient关键字 Java的serialization提供了一种持久化对象实例的机制.当持久化对象时,可能有一个特殊的对象数据成员,我们不想  用serialization机制来保存它.为 ...

  2. Java单例破坏以及防止

    单例实现的方式有很多种,如有需要,请查设计模式--单例_程序员Forlan的博客-CSDN博客 下面我们以静态内部类为例 public class Demo{private Demo(){}publi ...

  3. 2020-10-23 集合+序列化+递归+多线程+泛型+枚举+单例+反射小记

    [集合]: Collection接口 (Collection接口是集合类的根接口,Java中没有提供这个接口的直接的实现类.但是却让其被继承产生了两个接口,就是Set和List) Set接口(无序集合 ...

  4. 为什么我强烈建议大家使用枚举来实现单例

    转载自   为什么我墙裂建议大家使用枚举来实现单例 关于单例模式,我的博客中有很多文章介绍过.作为23种设计模式中最为常用的设计模式,单例模式并没有想象的那么简单.因为在设计单例的时候要考虑很多问题, ...

  5. 为什么我墙裂建议大家使用枚举来实现单例。

    关于单例模式,我的博客中有很多文章介绍过.作为23种设计模式中最为常用的设计模式,单例模式并没有想象的那么简单.因为在设计单例的时候要考虑很多问题,比如线程安全问题.序列化对单例的破坏等. 单例相关文 ...

  6. 懵了,Java枚举单例模式比DCL和静态单例要好???

    点击关注公众号,实用技术文章及时了解 来源:liuchenyang0515.blog.csdn.net/article/ details/121049426 文章目录 双重校验锁单例(DCL) 为什么 ...

  7. 静态内部类实现单例_为什么用枚举类来实现单例模式越来越流行?

    前言 单例模式是 Java 设计模式中最简单的一种,只需要一个类就能实现单例模式,但是,你可不能小看单例模式,虽然从设计上来说它比较简单,但是在实现当中你会遇到非常多的坑,所以,系好安全带,上车. 单 ...

  8. python3的配置文件类单例实现_单例模式的几种实现方式及对比

    来源:博客园 作者:为何不是梦 链接:https://www.cnblogs.com/ibigboy/p/11423613.html 所谓单例就是在系统中只有一个该类的实例. 单例模式的核心分以下三个 ...

  9. 【设计模式】单例(Singleton)

    在众多的设计模式中单例应该是最常见的设计模式了,对于一名初级工程师来说,这个设计模式可能是自己唯一能够拿的出手的设计模式.那么什么是单例模式呢?顾名思义单例就是单个实例,也就是说在整个类的使用中只允许 ...

最新文章

  1. DTCMS插件的制作实例电子资源管理(四)URL重写
  2. 华为备忘录导入印记云笔记_原来华为手机自带会议神器,开会不用手写,这个功能就能搞定...
  3. python write非法字符报错_Python爬虫实现的微信公众号文章下载器
  4. highcharts一天时间 与一周时间_如何规划自己一天的时间
  5. Android 开发工具类 36_ getSimSerial
  6. C++ Primer 5th笔记(chap 13 拷贝控制) 对象移动
  7. 关于CoordinatorLayout的用法——复杂交互的克星
  8. 《JavaScript 高级程序设计》笔记 第7章及以后
  9. 计算机高办报名时间,前方高能!计算机信息技术证报名入口、考试时间已发布...
  10. hql与sql的区别(转)
  11. Cakephp 创建无模型的Controller
  12. ad系统安装配置指南(java-jndi-ldap),AD系统安装配置指南(JAVA-JNDI-LDAP-Exchange)
  13. 使用 CleanWipe 解决Symantec Endpoint Protection卸载需要密码问题
  14. mysql中修改表字段的类型长度_mysql中修改表字段名/字段长度/字段类型详解
  15. QQ音乐爬虫之放弃的路
  16. 这个拥有中国血统的黑客,曾将美国搅得天翻地覆
  17. 网络不断电系统服务器ip,IP网络控制主机 T-7700N
  18. 刷完这50个标准库模块:没人比我更懂Python了
  19. 爬取武汉所有的公交站名
  20. 易地推招生拓客分享:如何让社群招生成为培训机构招生利器?

热门文章

  1. jvm性能调优 - 06线上应用部署JVM实战_堆内存预估与设置
  2. 白话Elasticsearch23-深度探秘搜索技术之通过ngram分词机制实现index-time搜索推荐
  3. Java学习笔记(十)--控制台输入输出
  4. h5实现网页内容跟随窗口大小移动_HTML5使用四种方法实现移动页面自适应手机屏幕的方法总结...
  5. 剑指offer 11. 旋转数组的最小数字(很详细!)
  6. 使用adb命令控制Android
  7. Halcon算子:min_max_gray和gray_histo的区别
  8. java邮件发送代码_用Java实现最简单的邮件发送代码
  9. h5页面提示只能在微信浏览器中打开_电子问卷h5怎么做?
  10. 小程序识别带多个参数二维码进入商品详情