详解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进行反编译生成.java文件方式(javap、jad下载安装使用)

    Java进行反编译生成.java文件方式(javap、jad下载安装使用)

    这篇文章主要介绍了Java进行反编译生成.java文件方式(javap、jad下载安装使用),具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-12-12
  • Java实现Map遍历key-value的四种方法

    Java实现Map遍历key-value的四种方法

    本文主要介绍了Java实现Map遍历key-value的四种方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-07-07
  • 计算两个字符串最大公有子串

    计算两个字符串最大公有子串

    本文主要介绍了计算两个字符串最大公有子串的解决方案。具有很好的参考价值,下面跟着小编一起来看下吧
    2017-01-01
  • 手把手教你写一个spring IOC容器的方法

    手把手教你写一个spring IOC容器的方法

    这篇文章主要介绍了手把手教你写一个spring IOC容器的方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-04-04
  • java8 stream多字段排序的实现

    java8 stream多字段排序的实现

    这篇文章主要介绍了java8 stream多字段排序的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-03-03
  • Java成员变量的隐藏(实例讲解)

    Java成员变量的隐藏(实例讲解)

    下面小编就为大家带来一篇Java成员变量的隐藏(实例讲解)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-09-09
  • Nacos配置文件使用经验及CAP原则详解

    Nacos配置文件使用经验及CAP原则详解

    这篇文章主要为大家介绍了Nacos配置文件使用经验及CAP规则详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2024-02-02
  • 解决使用@ResponseBody后返回500错误的问题

    解决使用@ResponseBody后返回500错误的问题

    这篇文章主要介绍了解决使用@ResponseBody后返回500错误的问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-09-09
  • JAVA根据ip地址获取归属地的实现方法

    JAVA根据ip地址获取归属地的实现方法

    本文主要介绍了JAVA根据ip地址获取归属地的实现方法,要通过Java程序获取IP地址对应的城市,需要借助第三方的IP地址库,下面就来介绍一下,感兴趣的可以了解一下
    2023-10-10
  • spring cloud实现Eureka注册中心的HA的方法

    spring cloud实现Eureka注册中心的HA的方法

    本篇文章主要介绍了spring cloud实现Eureka注册中心的HA的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-01-01

最新评论