(单例设计模式之一)饿汉式的反射与反序列化漏洞

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了(单例设计模式之一)饿汉式的反射与反序列化漏洞相关的知识,希望对你有一定的参考价值。

1.闲话少说,直接上代码。

import java.io.Serializable;
//饿汉式
public class Singleton01 implements Serializable{
    //1.私有的属性
    private static Singleton01 instance=new Singleton01();
    //2.私有的构造器
    private Singleton01(){}
    //3.共有的get()方法
    public static  Singleton01 getInstance(){//这里不需要加同步块synchronized
        return instance;
    }
}

为什么不需要加同步块?这里插播一条短消息

1.  饿汉式是天然的线程安全的,因此不需要加线程锁。饿汉式之所以是天然的线程安全,是因为:

一个字节码文件被加载到内存会经历以下过程:

1、链接(1、验证2、准备3、解析)

2、初始化

3、使用

4、卸载

初始化是执行类构造器<clinit>()方法的过程。类构造器<clinit>()方法是由编译器自动收集类中的所有类变量的赋 值动作和静态语句块(static)中的语句合并产生的。在这个过程中instance已经被实例化了。并且虚拟机JVM会保证一个类 的<clinit>()方法在多线程的环境中被正确加锁和同步。因此他是天然线程安全的。

 

2.下面代码分析如何使用反射破解懒汉式:

public class Singletons_Test_02 {

    public static void main(String[] args) throws Exception {

   Class<Singleton01> clazz = (Class<Singleton01>) Class.forName("com.shc.singleton.Singleton01");
        Constructor<Singleton01> c = clazz.getDeclaredConstructor(null);//获得无参构造器
        c.setAccessible(true);//跳过权限检查,访问私有的构造器
        Singleton01 s3 = c.newInstance();
        Singleton01 s4 = c.newInstance();
        System.out.println(s3);
        System.out.println(s4);

  }

}

此时输出s3与s4的哈希值是相等的。代表不是同一个对象,饿汉式单例通过反射破解完成。

3.下面分析饿汉式如何避免反射漏洞?

public class Singleton1 {

    //1.私有的属性
    private static Singleton1 instance=new Singleton1();
    //2.私有的构造器
    private Singleton1(){
        if (null != instance) {
            throw new RuntimeException();
        }
    }
    //3.共有的get()方法
    public static  Singleton1 getInstance(){//天然的线程安全,不需要加同步块,因此调用效率高
        return instance;
    }
}

在私有的构造器中加一个判断,判断要创建的对象是否存在,如果不存在再创建,已存在就抛出运行时异常。

4.下面利用序列化与反序列化漏洞破解饿汉式单例

public class Singletons_Test_03 {

    public static void main(String[] args) throws Exception {

    Singleton01 s1 = Singleton01.getInstance();
        Singleton01 s2 = Singleton01.getInstance();
        System.out.println(s1);
        System.out.println(s2);
        
        //序列化
        FileOutputStream fos = new FileOutputStream("../a.txt");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(s1);
        oos.close();
        fos.close();
        
        //反序列化
         ObjectInputStream ois =new ObjectInputStream(new FileInputStream("../a.txt"));
         Singleton01 s3 = (Singleton01) ois.readObject();
         System.out.println(s3);

  }

}

5.破解饿汉式反序列化漏洞

public class Singleton11 implements Serializable{
    //1.私有的属性
    private static Singleton11 instance=new Singleton11();
    //2.私有的构造器
    private Singleton11(){}
    //3.共有的get()方法
    public static Singleton11 getInstance(){
        return instance;
    }
    //反序列化时(加这个方法可以防止反序列化漏洞)
    private Object readResolve() throws ObjectStreamException{
        return instance;
    }
}

以上demo较为简单,不做深入研究,设计模式体验重在应用。

以上是关于(单例设计模式之一)饿汉式的反射与反序列化漏洞的主要内容,如果未能解决你的问题,请参考以下文章

单例模式的N种场景N种写法

单例模式,反射破环?

单例模式,反射破环?

关于Java单例模式中懒汉式和饿汉式的两种类创建方法

单例模式防反射及性能

单例模式总结