通过反射机制、序列化反序列化破解单例模式

一、懒汉式单例

package edu.aeon.model.singleton;
/**
 * 单例模式:懒汉式(类装载及创建)
 * 步骤:
 *     1.构造器私有化(防止外部new)
 *     2.提供静态的、私有的代表该对象的实例的空引用
 *     3.提供静态的、公开的函数(方法)供外部访问
 * @author aeon
 */
public class LazySingleton {
    //1.构造器私有化(防止外部new)
    private LazySingleton(){}
    //2.提供静态的、私有的代表该对象的实例的空引用(调用时才给值)
    private static LazySingleton lazySingletonInstance=null;
    //3.提供静态的、公开的函数(方法)供外部访问应用该模式唯一实例
    public static LazySingleton getLazySingletonInstance(){
        if(null==lazySingletonInstance){
            lazySingletonInstance=new LazySingleton();
        }
        return lazySingletonInstance;
    }
}

二、破解单例模式(除枚举)

  2.1通过反射机制来破解上面提供的懒汉式单例  

package edu.aeon.model.singleton.crack;

import java.lang.reflect.Constructor;

import edu.aeon.model.singleton.LazySingleton;

/**
 * [说明]:通过反射机制破解单例模式(除枚举)
 * 步骤:
 *     1.加载单例类
 *     2.通过反射获得无参构造器
 *     3.解除本类访问单例类私有属性的限制(跳过权限的检查)。
 *     4.通过反射获得的无参构造器来获得实例
 *     5.对比获得实例是否为同一对象
 * @author aeon
 *
 */
public class CrackSingletonByReflex {

    public static void main(String[] args) {
        System.out.println("破解前:"+(LazySingleton.getLazySingletonInstance()==LazySingleton.getLazySingletonInstance()?"是同一对象!":"不是同一对象!"));
        try {
            //1.加载单例类
            Class<LazySingleton> clazz = (Class<LazySingleton>) Class.forName("edu.aeon.model.singleton.LazySingleton");
            //2.通过反射获得无参构造器
            Constructor<LazySingleton> constructor= clazz.getDeclaredConstructor();
            //3.解除本类访问单例类私有属性的限制(跳过权限的检查)。
            constructor.setAccessible(true);
            //4.通过反射获得的无参构造器来获得实例
            LazySingleton lazySingleton1=(LazySingleton) constructor.newInstance();
            LazySingleton lazySingleton2=(LazySingleton) constructor.newInstance();
            //5.对比获得实例是否为同一对象
            System.out.println("破解后:"+(lazySingleton1==lazySingleton2?"是同一对象!":"不是同一对象!"));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

  运行结果截图如下:

  

   很明显的看到通过反射机制可以破坏这种单例模式的本质。那么如何防止呢?

    2.1.1防止反射机制破解单例模式

package edu.aeon.model.singleton;
/**
 * 单例模式:懒汉式(类装载及创建)
 * 步骤:
 *     1.构造器私有化(防止外部new)
 *     2.提供静态的、私有的代表该对象的实例的空引用
 *     3.提供静态的、公开的函数(方法)供外部访问
 * @author aeon
 */
public class LazySingleton {
    //1.构造器私有化(防止外部new)
    private LazySingleton(){
        if(null!=lazySingletonInstance){
            throw new RuntimeException("单例模式不允许外界使用构造器!");
        }
    }
    //2.提供静态的、私有的代表该对象的实例的空引用(调用时才给值)
    private static LazySingleton lazySingletonInstance=null;
    //3.提供静态的、公开的函数(方法)供外部访问应用该模式唯一实例
    public static LazySingleton getLazySingletonInstance(){
        if(null==lazySingletonInstance){
            lazySingletonInstance=new LazySingleton();
        }
        return lazySingletonInstance;
    }
}

  我们再次运行通过反射机制破解单例模式程序、可以发现:

  


  原理:当存在这个单例的唯一实例的时候,通过反射机制跳过权限检查再次使用构造器构造对象的时候,通过抛出运行时异常的形式来阻止外界使用构造器来构造实例。

  2.2通过序列化/反序列化破解单例模式  

package edu.aeon.model.singleton.crack;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import edu.aeon.model.singleton.LazySingleton;

/**
 * [说明]:通过序列化/反序列化破解单例模式(除枚举) 
 * 步骤:
 *  1.序列化单例类对象实例 
 *  2.通过刚才生成的序列化对象文件反序列化生成对象
 * @author aeon
 *
 */
public class CrackSingletonBySerialize {

    public static void main(String[] args) {
        LazySingleton lazySingleton1 = LazySingleton.getLazySingletonInstance();
        LazySingleton lazySingleton2 = LazySingleton.getLazySingletonInstance();
        System.out.println((lazySingleton1 == lazySingleton2 ? "破解前是同一对象!" : "破解前不是同一对象!"));
        // 1.序列化单例类对象实例
        FileOutputStream fileOutputStream = null;
        ObjectOutputStream objectOutputStream = null;
        FileInputStream fileInputStream = null;
        ObjectInputStream objectInputStream = null;
        try {
            fileOutputStream = new FileOutputStream("b:/lazySingletonInstance.txt");
            objectOutputStream = new ObjectOutputStream(fileOutputStream);
            objectOutputStream.writeObject(lazySingleton1);
            System.out.println("序列化对象成功!");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                objectOutputStream.close();
            } catch (IOException e1) {
                e1.printStackTrace();
            }
            try {
                fileOutputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        // 2.通过刚才生成的序列化对象文件反序列化生成对象
        try {
            fileInputStream = new FileInputStream("b:/lazySingletonInstance.txt");
            objectInputStream = new ObjectInputStream(fileInputStream);
            LazySingleton lazySingleton3 = (LazySingleton) objectInputStream.readObject();
            System.out.println((lazySingleton1 == lazySingleton3 ? "破解后是同一对象!" : "破解后不是同一对象!"));
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                objectInputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                fileInputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }
}

  运行结果截图如下:

  

  2.2.1如何防止序列化/反序列化破解单例模式   

package edu.aeon.model.singleton;

import java.io.Serializable;

/**
 * 单例模式:懒汉式(类装载及创建)
 * 步骤:
 *     1.构造器私有化(防止外部new)
 *     2.提供静态的、私有的代表该对象的实例的空引用
 *     3.提供静态的、公开的函数(方法)供外部访问
 * @author aeon
 */
public class LazySingleton implements Serializable {
    //1.构造器私有化(防止外部new)
    private LazySingleton(){
        if(null!=lazySingletonInstance){
            throw new RuntimeException("单例模式不允许外界使用构造器!");
        }
    }
    //2.提供静态的、私有的代表该对象的实例的空引用(调用时才给值)
    private static LazySingleton lazySingletonInstance=null;
    //3.提供静态的、公开的函数(方法)供外部访问应用该模式唯一实例
    public static LazySingleton getLazySingletonInstance(){
        if(null==lazySingletonInstance){
            lazySingletonInstance=new LazySingleton();
        }
        return lazySingletonInstance;
    }
    /**
     * 反序列化时通过该默认方法返回对象
     * @return
     */
    private  Object readResolve(){
        return lazySingletonInstance;
    }
}

  我们再运行2.2代码、运行结果截图:

    


 原理:如果反序列化时定义了readResolve()方法、则会通过该方法返回对象的实例,而不是再序列化一个新对象返回、所以能保持返回的是同一对象的实例。

 这样就解决了单例模式中反射机制、序列化/反序列化漏洞。

如有任何疑问可联系邮箱: 给我发邮件、或直接联系QQ:1584875179 || 点返回首页

原文地址:https://www.cnblogs.com/aeon/p/10212065.html