详解Java序列化如何破坏单例模式

 更新时间:2023年12月15日 08:15:55   作者:大明哥_  
这篇文章主要为大家详细介绍了Java序列化是如何破坏单例模式的,文中的示例代码讲解详细,具有一定的借鉴价值,感兴趣的小伙伴可以学习一下

先看代码:

public class Singleton {
    private Singleton(){}

    private static class SingletonInstance{
        private static final Singleton instance = new Singleton();
    }

    public static Singleton getInstance(){
        return SingletonInstance.instance;
    }
}

我想应该没有不知道这行个类是干嘛的小伙伴了吧,这是单例模式的一种写法。

单例模式是每一个 Java boy 必须要掌握的设计模式,它所描述的是在某个进程内,某个类有且仅有一个实例。我们知道要破坏单例模式就必须让它创建多个对象。创建对象的方式无非就几种:

  • new
  • clone
  • 反射
  • 反序列化

首先单例模式的构造器一定是 private 的,所以 new 这种方式是无法破坏单例模式的。 而 clone 需要实现 Cloneable 接口,单例模式谁如果实现了这个接口,请打死它。所以就剩下反射和反序列化了。本篇文章只讨论反序列化。

反序列化破坏单例模式

与 clone 方式一样,反序列化需要实现 Serializable 接口,但是有小伙伴可能会说,谁会在单例模式中实现 Serializable 接口咯,除非他疯了,确实是这种情况,但是在实际情况中它并不是一定会避免的,有些类它就是一定要序列化。比如单例对象在不同环境或应用实例之间的共享、持久化或状态恢复,当然这些场景都属于比较特殊的场景。

继续用上面例子:

public class SerializableSingleton implements Serializable {
    // 省略部分代码
}

然后在对该类进行序列化和反序列化

public class Test {
    public static void main(String[] args) throws Exception {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("Singleton.txt"));
        SerializableSingleton singleton = SerializableSingleton.getInstance();
        oos.writeObject(singleton);

        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("Singleton.txt"));
        SerializableSingleton singleton1 = (SerializableSingleton) ois.readObject();

        System.out.println("singleton = singleton1:" + (singleton == singleton1));
    }
}

运行结果

singleton = singleton1:false

通过对 Singleton 进行反序列化得到了一个全新的对象,这就破坏了 Singleton 的单例性了。我们看 readObject() 源码就知道了。

    public final Object readObject() throws IOException, ClassNotFoundException {
        //...
        int outerHandle = passHandle;
        try {
            Object obj = readObject0(false);
            //...
            return obj;
        } finally {
            //...
        }
    }

调用 readObject0()

    private Object readObject0(boolean unshared) throws IOException {
        // ...
        try {
            switch (tc) {
                // ...

                case TC_OBJECT:
                    // readObject0()
                    return checkResolve(readOrdinaryObject(unshared));
                // ...
            }
        } finally {
            depth--;
            bin.setBlockDataMode(oldMode);
        }
    }

readObject0() 是根据反序列化对象的不同执行不同的方法来反序列化一个实例对象。我们这里是 Object,所以进一步看 readOrdinaryObject()

    private Object readOrdinaryObject(boolean unshared) throws IOException {
        // ...

        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);
        }
        
        // ...

        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;
    }

这段代码最核心的地方就是:

obj = desc.isInstantiable() ? desc.newInstance() : null;

底层依然是利用反射的方式来创建一个新对象。

那么对于这种方式有什么保护措施没?在 readOrdinaryObject() 最后面一段就已经告知了:

if (obj != null && handles.lookupException(passHandle) == null &&
      desc.hasReadResolveMethod()) {
     //....
 }        

判断反序列化的类是否已实现了 readResolve() ,如果有则会调用该方法,我们只需要在该方法里面返回原对象就可以了。验证下。

public class SerializableSingleton implements Serializable {
    // ...
    private Object readResolve() {
        return SingletonInstance.instance;
    }
}

执行结果:

singleton = singleton1:true

执行结果为 true,就说明序列化和反序列化出来的是同一个对象。

所以,要想防止单例被反序列化破坏,就让单例实现 readResolve()方法,返回同一个对象即可。

到此这篇关于详解Java序列化如何破坏单例模式的文章就介绍到这了,更多相关Java序列化内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • java字符串日期类Date和Calendar相互转化及相关常用方法

    java字符串日期类Date和Calendar相互转化及相关常用方法

    Java语言的Calendar(日历),Date(日期),和DateFormat(日期格式)组成了Java标准的一个基本但是非常重要的部分,下面这篇文章主要给大家介绍了关于java字符串日期类Date和Calendar相互转化及相关常用方法的相关资料,需要的朋友可以参考下
    2023-12-12
  • Java常用类之日期相关类使用详解

    Java常用类之日期相关类使用详解

    这篇文章主要为大家介绍了Java中常用类的日期相关类的用法教程,文中的示例代码讲解详细,对我们学习Java有一定的帮助,需要的可以参考一下
    2022-08-08
  • 使用JMX连接JVM实现过程详解

    使用JMX连接JVM实现过程详解

    这篇文章主要介绍了使用JMX连接JVM实现过程详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-04-04
  • Mabatis错误提示Parameter index out of range的处理方法

    Mabatis错误提示Parameter index out of range的处理方法

    这篇文章主要介绍了Mabatis错误提示Parameter index out of range 的处理方法,本文给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下
    2018-08-08
  • MybatisPlus调用原生SQL的实现方法

    MybatisPlus调用原生SQL的实现方法

    本文主要介绍了MybatisPlus调用原生SQL的实现方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-02-02
  • Java实现雪花算法的示例代码

    Java实现雪花算法的示例代码

    SnowFlow算法是Twitter推出的分布式id生成算法,主要核心思想就是利用64bit的long类型的数字作为全局的id。本文将用Java语言实现雪花算法,感兴趣的可以学习一下
    2022-03-03
  • Java中HttpServletRequestWrapper的使用与原理详解

    Java中HttpServletRequestWrapper的使用与原理详解

    这篇文章主要介绍了Java中HttpServletRequestWrapper的使用与原理详解,HttpServletRequestWrapper 实现了 HttpServletRequest 接口,可以让开发人员很方便的改造发送给 Servlet 的请求,需要的朋友可以参考下
    2024-01-01
  • springsecurity实现拦截器的使用示例

    springsecurity实现拦截器的使用示例

    Spring Security 可以替代拦截器,同时还可以提供更加细粒度的权限控制和身份认证,本文就来介绍一下springsecurity实现拦截器的使用示例,感兴趣的可以了解一下
    2023-10-10
  • Spring创建bean实例的几种方式分享

    Spring创建bean实例的几种方式分享

    这篇文章主要介绍了Spring创建bean实例的几种方式分享,文章围绕主题展开详细的内容介绍,具有一定的参考价值,需要的小伙伴可以参考一下
    2022-07-07
  • springboot+springmvc实现登录拦截

    springboot+springmvc实现登录拦截

    这篇文章主要介绍了springboot+springmvc实现登录拦截,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-10-10

最新评论