多线程下的单例模式——攻破单例模式(二)

上篇文章讲到常见的两种单例模式。尽管实现了单例模式在多线程环境下的绝对安全,但是想破坏已经写好单例模式也很简单。

1.反射攻击

上代码:

import java.lang.reflect.Constructor;

/**
 * 恶汉模式,加上final不能被继承
 */
public final class HungrySingleton {
    //1.私有,静态的变量。类在加载的时候就创建
    private static HungrySingleton singleton = new HungrySingleton();
    //2.私有构造方法,不允许其他人再来创建对象
    private HungrySingleton(){}
 
    /**
     * 3.提供一个获取实例的方法
     * @return
     */
    public static HungrySingleton getInstance(){
        return singleton;
    }


    public static void main(String[] args) throws Exception{
        //单例模式获取到的实例
        HungrySingleton instance = HungrySingleton.getInstance();
        System.out.println("单例模式获取到的实例:" + instance);

        /**
         * 反射攻破
         */
        //获取构造器
        Constructor<?>[] constructors = HungrySingleton.class.getDeclaredConstructors();
        //破防
        constructors[0].setAccessible(true);
        //获取新实例
        HungrySingleton instance1 = (HungrySingleton)constructors[0].newInstance();
        System.out.println("反射后得到的实例:" + instance1);

    }
}

看效果:

 可以发现两个对象是不一样的,通过反射可以获取额外的对象,那如何解决呢,其实很简单。只需要再构造方法中加入判断逻辑即可:


public final class HungrySingleton {
    //1.私有,静态的变量。类在加载的时候就创建
    private static HungrySingleton singleton = new HungrySingleton();

    private static boolean flag = true;

    //2.私有构造方法,不允许其他人再来创建对象
    private HungrySingleton(){
        if(flag){
            flag = false;
        }else {
            throw new RuntimeException("禁止访问私有构造方法");
        }
    }

    /**
     * 3.提供一个获取实例的方法
     * @return
     */
    public static HungrySingleton getInstance(){
        return singleton;
    }

看效果:

 当反射调用构造方法,判断实例是否已经存在,存在就直接抛异常即可。

2.序列化攻击

我们可以通过一些序列化方式来让java对象以某种格式进行存储或者传输,常见的有java自带的对象流啊,fastjson,hessian等等,但是当把一个单例对象序列化后再反序列化,还是原来的对象吗?上代码:

import java.io.*;
import java.lang.reflect.Constructor;

/**
 * 恶汉模式,加上final不能被继承
 */
public final class HungrySingleton1 implements Serializable {
    //1.私有,静态的变量。类在加载的时候就创建
    private static HungrySingleton1 singleton = new HungrySingleton1();
    //2.私有构造方法,不允许其他人再来创建对象
    private HungrySingleton1(){
        if(singleton != null){
            throw new RuntimeException("禁止访问始有构造方法");
        }
    }

    /**
     * 3.提供一个获取实例的方法
     * @return
     */
    public static HungrySingleton1 getInstance(){
        return singleton;
    }


    public static void main(String[] args) throws Exception{
        //单例模式获取到的实例
        HungrySingleton1 instance = HungrySingleton1.getInstance();
        System.out.println("单例模式获取到的实例:" + instance);

        /**序列化攻击,这里使用的java自带的对象流
         */
        ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("obj.txt"));
        outputStream.writeObject(instance);
        outputStream.flush();
        outputStream.close();

        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("obj.txt"));
        HungrySingleton1 instance1 = (HungrySingleton1) objectInputStream.readObject();
        System.out.println("反序列化后得到的对象:" + instance1);
    }
}

看结果:

同样的可以得到除了单例模式以外的另一个对象。这到底是什么原因呢,我们来看看

HungrySingleton1 instance1 = (HungrySingleton1) objectInputStream.readObject();

这句代码底层做了啥。进入readObject()源码,看期中一段:

 try {
            //这里得到了obj对象
            Object obj = readObject0(false);
            handles.markDependency(outerHandle, passHandle);
            ClassNotFoundException ex = handles.lookupException(passHandle);
            if (ex != null) {
                throw ex;
            }
            if (depth == 0) {
                vlist.doCallbacks();
            }
            //返回的obj就是反序列化后得到的obj
            return obj;
}

这里就需要继续进入   Object obj = readObject0(false);其中有一段枚举

 case TC_OBJECT:
                    return checkResolve(readOrdinaryObject(unshared));

再次进入readOrdinaryObject(unshared):最终可以看到最核心的一段代码

 Object obj;
        try {
            //这是最重要的一行
            //desc.isInstantiable()判断对象是否可实例化,其实就是判断能否获取到构造法
            //desc.newInstance()就是在给我们创建一个新的对象
            obj = desc.isInstantiable() ? desc.newInstance() : null;
        } catch (Exception ex) {
            throw (IOException) new InvalidClassException(
                desc.forClass().getName(),
                "unable to create instance").initCause(ex);
        }

所底层是帮助new了一个对象返回,自然就会产生一个除了单例以外的一个对象。

解决:在readOrdinaryObject(unshared)中有这么一段:

Object rep = desc.invokeReadResolve(obj);

回调对象中的readResolve()方法。此时我们只需要在对象中加入这个方法即可

上代码:

public final class HungrySingleton1 implements Serializable {
    //1.私有,静态的变量。类在加载的时候就创建
    private static HungrySingleton1 singleton = new HungrySingleton1();

    //2.私有构造方法,不允许其他人再来创建对象
    private HungrySingleton1(){
        if(singleton != null){
            throw new RuntimeException("禁止访问始有构造方法");
        }
    }

    //反序列化会调用这个方法,只需要把我们自己的对象返回即可
    Object readResolve(){
        return singleton;
    }
}

看效果

 这样就解决啦。

建议单例直接使用枚举方式,枚举是不能反射创建对象的,JDK会抛出异常,同时反序列化后也是同一个。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值