java反射破解单例模式

代码演示

// 懒汉式: 用的时候创建
public class LazyMan {
    private LazyMan() {
        synchronized (LazyMan.class) {
        }
    }

    private volatile static LazyMan lazyMan;

    // 双重检验锁 DCL懒汉
    public static LazyMan getLazyMan() {
        if (lazyMan == null) {
            synchronized (LazyMan.class) {
                if (lazyMan == null) {
                    try {
                        TimeUnit.SECONDS.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    lazyMan = new LazyMan();
                }
            }
        }
        return lazyMan;
    }


    /**
     * 反射破环单例模式
     * @param args
     */
    public static void main(String[] args) throws NoSuchMethodException, 
    IllegalAccessException, InvocationTargetException, InstantiationException {
    	// 正常获取实例
        LazyMan lazyMan1 = LazyMan.getLazyMan();

        // 使用反射创建LazyMan实例
        Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        // 通过反射获取的实例
        LazyMan lazyMan2 = declaredConstructor.newInstance();
        System.out.println(lazyMan1 == lazyMan2);
    }

}
  • 控制台输出结果false, 表示两个实例不是同一个对象
false

如何避免

  • (1)修改构造方法(不能完全避免)
private LazyMan() {
        synchronized (LazyMan.class) {
            if (lazyMan != null) {
                throw new RuntimeException("不要试图破坏单例模式");
            }
        }
    }

这种避免的前提式final的lazyMan已经被new出来的, 如果使用反射的方式生成两个实例, 依然不满足单例,代码如下

	/**
     * 反射破环单例模式
     * @param args
     */
    public static void main(String[] args) throws NoSuchMethodException,
     IllegalAccessException, InvocationTargetException, InstantiationException {
        // 使用反射创建LazyMan实例
        Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        //通过反射创建两个实例对象
        LazyMan lazyMan2 = declaredConstructor.newInstance();
        LazyMan lazyMan3 = declaredConstructor.newInstance();
        System.out.println(lazyMan3 == lazyMan2);
    }
  • 控制台输出结果false, 表示两个实例不是同一个对象
false
  • (2)继续修改构造方法(还是不能完全解决)
// 添加一个标志位, 当第一个实例生成后, flag变为true, 以后构造方法不能在生成实例
private static boolean flag = false;

private LazyMan() {
        synchronized (LazyMan.class) {
            if (flag == false){
                flag = true;
            } else{
                throw new RuntimeException("不要试图破坏单例模式");
            }
        }
    }
  • 按照上面的方式修改了构造器后, 还是可以通过反射破解, 代码如下
	/**
     * 反射破环单例模式
     * @param args
     */
    public static void main(String[] args) throws NoSuchMethodException, 
    IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
        // 使用反射创建LazyMan实例

        // 通过反射获取flag,并破坏私有权限
        Field flag = LazyMan.class.getDeclaredField("flag");
        flag.setAccessible(true);

        Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        LazyMan lazyMan2 = declaredConstructor.newInstance();
        // 在创建lazyMan2实例的时候将flag的值改为false
        flag.set(lazyMan2, false);
        LazyMan lazyMan3 = declaredConstructor.newInstance();
        System.out.println(lazyMan3 == lazyMan2);
    }
  • 控制台输出结果false, 表示两个实例不是同一个对象
false

到底如何彻底解决呢

反射的newInstance()源码分析, 如果是枚举类则不能通过反射创建实例

@CallerSensitive
    public T newInstance(Object ... initargs)
        throws InstantiationException, IllegalAccessException,
               IllegalArgumentException, InvocationTargetException
    {
        if (!override) {
            if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
                Class<?> caller = Reflection.getCallerClass();
                checkAccess(caller, clazz, null, modifiers);
            }
        }
		// 如果是枚举类则不能通过反射创建实例
        if ((clazz.getModifiers() & Modifier.ENUM) != 0)
            throw new IllegalArgumentException("Cannot reflectively create enum objects");
        ConstructorAccessor ca = constructorAccessor;   // read volatile
        if (ca == null) {
            ca = acquireConstructorAccessor();
        }
        @SuppressWarnings("unchecked")
        T inst = (T) ca.newInstance(initargs);
        return inst;
    }
  • 通过枚举来生成单例
// 枚举本身是什么,  也是一个CLASS类
public enum EnumSingle {
    INSTANCE;

    public EnumSingle getInstance(){
        return EnumSingle.INSTANCE;
    }
}

class Test{
    public static void main(String[] args) {
        EnumSingle instance1 = EnumSingle.INSTANCE;
        EnumSingle instance2 = EnumSingle.INSTANCE;
        System.out.println(instance1 == instance2);
    }
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值