概述
前言
今天[2019-04-01]在某位大大的"聊天" 过程中提到了这么一个问题, 如果让你选择 单例的实现, 你会怎么选择
我就不假思索的说了一个 使用静态内部类来创建 单例对象吧
通常创建单例对象 有几种方式 : 双检锁 + volatile, 静态内部类holder, 枚举
然后 大大又问了一下, 你选的这种方式在 反射 和 反序列化 的情况下 会怎么样呢?, 拦不拦得住呢 ?
然后 我脑袋里面想了一下, 反射过来, 静态内部类holder 这种方式怕是gg了吧, 能够通过反射创建出多个对象, 并且 通过反序列化 也是能够创建出多个改类型的对象的
然后 考虑了一下 枚举, 枚举 我记得 构造方法在规范上面约定的必须是 private, 然后使用反射调用 好像也记得有约束, 然后 反序列化这边, 我记得 之前看过这块地代码, 是构造对象是调用的 枚举的 "valueOf" 方法, 返回的是已经构造好枚举对象
但是回答问题的时候, 又说枚举, 就给人一种很像是猜的结果的感觉 ...
然后 针对这些东西 我们这里回来进行一些 hack, 希望能够打破所谓的只能存在一个实例的说法
主要测试的方式是 : 通过反射处理, 通过反序列化处理, 通过 unsafe 处理
一下内容仅仅是测试, 请勿在实际生产环境中骚操作
以下测试代码, 截图 基于 jdk 8
case01 双检锁单例
package com.hx.test04;
import sun.misc.Unsafe;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
/**
* DoubleCheckSingleton
*
* @author Jerry.X.He <970655147@qq.com>
* @version 1.0
* @date 2020-04-01 17:01
*/
public class Test18DoubleCheckSingleton implements Serializable {
// INSTANCE
private static volatile Test18DoubleCheckSingleton INSTANCE;
// disable constructor
private Test18DoubleCheckSingleton() {
// throw new RuntimeException("can't instantiate !");
System.out.println(" <init> called ");
}
// Test18DoubleCheckSingleton
public static void main(String[] args) throws Exception {
Test18DoubleCheckSingleton entity01 = Test18DoubleCheckSingleton.getInstance();
// case 1 constructor
Class<Test18DoubleCheckSingleton> clazz = Test18DoubleCheckSingleton.class;
Constructor<Test18DoubleCheckSingleton> constructor = clazz.getDeclaredConstructor();
Test18DoubleCheckSingleton entity02 = constructor.newInstance();
// case 2 unsafe
Unsafe unsafe = getUnsafe();
Test18DoubleCheckSingleton entity03 = (Test18DoubleCheckSingleton) unsafe.allocateInstance(Test18DoubleCheckSingleton.class);
// case 3 deserialize
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(entity01);
byte[] serialized = baos.toByteArray();
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(serialized));
Test18DoubleCheckSingleton entity04 = (Test18DoubleCheckSingleton) ois.readObject();
int x = 1;
}
/**
* getInstance
*
* @return com.hx.test04.Test18DoubleCheckSingleton
* @author Jerry.X.He<970655147@qq.com>
* @date 2020-04-01 17:02
*/
public static Test18DoubleCheckSingleton getInstance() {
if(INSTANCE == null) {
synchronized (Test18DoubleCheckSingleton.class) {
if(INSTANCE == null) {
INSTANCE = new Test18DoubleCheckSingleton();
}
}
}
return INSTANCE;
}
/**
* getUnsafe
*
* @return sun.misc.Unsafe
* @author Jerry.X.He<970655147@qq.com>
* @date 2020-04-01 17:11
*/
public static Unsafe getUnsafe() {
try {
Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
unsafeField.setAccessible(true);
return (Unsafe) unsafeField.get(null);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
}
可以看出, 出了 getInstance 拿到的一个对象之外
另外的三种 hack 方式 也拿到了一个新建的对象
日志这边打印了两次构造方法中的输出, 具体的原因可以参见 : 42 不调用给定类的构造方法创建给定类的对象
case02 静态内部类 Holder
package com.hx.test04;
import sun.misc.Unsafe;
import java.io.*;
import java.lang.reflect.Constructor;
/**
* DoubleCheckSingleton
*
* @author Jerry.X.He <970655147@qq.com>
* @version 1.0
* @date 2020-04-01 17:01
*/
public class Test19StaticClazzHolderSingleton implements Serializable {
// INSTANCE
private static volatile Test19StaticClazzHolderSingleton INSTANCE;
// disable constructor
private Test19StaticClazzHolderSingleton() {
// throw new RuntimeException("can't instantiate !");
System.out.println(" <init> called ");
}
// Test18DoubleCheckSingleton
public static void main(String[] args) throws Exception {
Test19StaticClazzHolderSingleton entity01 = Test19StaticClazzHolderSingleton.getInstance();
// case1 constructor
Class<Test19StaticClazzHolderSingleton> clazz = Test19StaticClazzHolderSingleton.class;
Constructor<Test19StaticClazzHolderSingleton> constructor = clazz.getDeclaredConstructor();
Test19StaticClazzHolderSingleton entity02 = constructor.newInstance();
// case2 unsafe
Unsafe unsafe = Test18DoubleCheckSingleton.getUnsafe();
Test19StaticClazzHolderSingleton entity03 = (Test19StaticClazzHolderSingleton) unsafe.allocateInstance(Test19StaticClazzHolderSingleton.class);
// case 3 deserialize
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(entity01);
byte[] serialized = baos.toByteArray();
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(serialized));
Test19StaticClazzHolderSingleton entity04 = (Test19StaticClazzHolderSingleton) ois.readObject();
int x = 1;
}
/**
* getInstance
*
* @return com.hx.test04.Test18DoubleCheckSingleton
* @author Jerry.X.He<970655147@qq.com>
* @date 2020-04-01 17:02
*/
public static Test19StaticClazzHolderSingleton getInstance() {
return SingletonHolder.INSTANCE;
}
/**
* SingletonHolder
*
* @author Jerry.X.He <970655147@qq.com>
* @version 1.0
* @date 2020-04-01 18:07
*/
private static class SingletonHolder {
// instance
private static Test19StaticClazzHolderSingleton INSTANCE = new Test19StaticClazzHolderSingleton();
}
}
可以看到这种方式也沦陷了, 三种 hack 方式分别创建了 三个对象
case03 枚举单例
package com.hx.test04;
import com.sun.org.apache.bcel.internal.classfile.ConstantClass;
import mockit.Mock;
import mockit.MockUp;
import sun.misc.Unsafe;
import sun.reflect.ConstructorAccessor;
import sun.reflect.Reflection;
import java.io.*;
import java.lang.reflect.*;
/**
* DoubleCheckSingleton
*
* @author Jerry.X.He <970655147@qq.com>
* @version 1.0
* @date 2020-04-01 17:01
*/
public enum Test20EnumSingleton implements Serializable {
INSTANCE;
// disable constructor
private Test20EnumSingleton() {
// throw new RuntimeException("can't instantiate !");
System.out.println(" <init> called ");
}
// Test18DoubleCheckSingleton
public static void main(String[] args) throws Exception {
Test20EnumSingleton entity01 = Test20EnumSingleton.getInstance();
// case1 constructor
// Cannot reflectively create enum objects
Class<Test20EnumSingleton> clazz = Test20EnumSingleton.class;
Constructor<Test20EnumSingleton> constructor = clazz.getDeclaredConstructor(String.class, int.class);
Method acquireConstructorAccessorMethod = Constructor.class.getDeclaredMethod("acquireConstructorAccessor");
acquireConstructorAccessorMethod.setAccessible(true);
acquireConstructorAccessorMethod.invoke(constructor);
Field constructorAccessorField = Constructor.class.getDeclaredField("constructorAccessor");
constructorAccessorField.setAccessible(true);
ConstructorAccessor constructorAccessor = (ConstructorAccessor) constructorAccessorField.get(constructor);
Test20EnumSingleton entity02 = (Test20EnumSingleton) constructorAccessor.newInstance(new Object[]{"xyz", 2});
// Test20EnumSingleton entity02 = constructor.newInstance("xyz", 2);
// case2 unsafe
Unsafe unsafe = Test18DoubleCheckSingleton.getUnsafe();
Test20EnumSingleton entity03 = (Test20EnumSingleton) unsafe.allocateInstance(Test20EnumSingleton.class);
// case 3 deserialize
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(entity01);
byte[] serialized = baos.toByteArray();
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(serialized));
Test20EnumSingleton entity04 = (Test20EnumSingleton) ois.readObject();
int x = 1;
}
/**
* getInstance
*
* @return com.hx.test04.Test18DoubleCheckSingleton
* @author Jerry.X.He<970655147@qq.com>
* @date 2020-04-01 17:02
*/
public static Test20EnumSingleton getInstance() {
return INSTANCE;
}
}
枚举这里通过反射来 hack 的话, 直接通过 Constructor.newInstance 会存在问题, 会被校验拦住
然后调整 clazz.getModifiers 似乎是又有些难度, 怕是要调试 更新运行时的数据, 所以 选择了另外的一种 hack 方式
获取对应的 ConstructorAccessor, 来创建实例, 当然 这里 hack 成功了
另外 unsafe 这里也 hack 成功了, 只不过类型的相关字段为 null, 0 等
再看一下 反序列化这边, 我们发现 entity04 是和 entity01 是同一个对象, 因此 反序列化这边 枚举拦住了
枚举的反序列化这边具体在代码里面的体现为
case04 双检锁单例构造方法抛出异常
package com.hx.test04;
import sun.misc.Unsafe;
import java.io.*;
/**
* DoubleCheckSingleton
*
* @author Jerry.X.He <970655147@qq.com>
* @version 1.0
* @date 2020-04-01 17:01
*/
public class Test21ConstructorExSingleton extends Test21ConstructorExSingletonParent implements Serializable {
// INSTANCE
private static volatile Test21ConstructorExSingleton INSTANCE;
// disable constructor
private Test21ConstructorExSingleton() {
}
// Test18DoubleCheckSingleton
public static void main(String[] args) throws Exception {
Test21ConstructorExSingleton entity01 = Test21ConstructorExSingleton.getInstance();
// case1 constructor
// Class<Test21ConstructorExSingleton> clazz = Test21ConstructorExSingleton.class;
// Constructor<Test21ConstructorExSingleton> constructor = clazz.getDeclaredConstructor();
// Test21ConstructorExSingleton entity02 = constructor.newInstance();
// case2 unsafe
Unsafe unsafe = Test18DoubleCheckSingleton.getUnsafe();
Test21ConstructorExSingleton entity03 = (Test21ConstructorExSingleton) unsafe.allocateInstance(Test21ConstructorExSingleton.class);
// case 3 deserialize
// ByteArrayOutputStream baos = new ByteArrayOutputStream();
// ObjectOutputStream oos = new ObjectOutputStream(baos);
// oos.writeObject(entity01);
// byte[] serialized = baos.toByteArray();
// ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(serialized));
// Test21ConstructorExSingleton entity04 = (Test21ConstructorExSingleton) ois.readObject();
int x = 1;
}
/**
* getInstance
*
* @return com.hx.test04.Test18DoubleCheckSingleton
* @author Jerry.X.He<970655147@qq.com>
* @date 2020-04-01 17:02
*/
public static Test21ConstructorExSingleton getInstance() {
if(INSTANCE == null) {
synchronized (Test21ConstructorExSingleton.class) {
if(INSTANCE == null) {
INSTANCE = new Test21ConstructorExSingleton();
}
}
}
return INSTANCE;
}
}
/**
* Test21ConstructorExSingletonParent
*
* @author Jerry.X.He <970655147@qq.com>
* @version 1.0
* @date 2020-04-01 19:18
*/
class Test21ConstructorExSingletonParent {
// INSTANCE_CREATED
private static volatile boolean INSTANCE_CREATED = false;
public Test21ConstructorExSingletonParent() {
synchronized (Test21ConstructorExSingletonParent.class) {
if (INSTANCE_CREATED) {
throw new RuntimeException("can't instantiate !");
}
INSTANCE_CREATED = true;
}
}
}
需要一个辅助的 parent 来处理 反序列化的场景[42 不调用给定类的构造方法创建给定类的对象], 因此这里增加了一个 Test21ConstructorExSingletonParent 来处理, 如果是实例已经创建, 则抛出异常
然后这里是吧 反射 和 反序列化 的 hack 都拦住了
unsafe 是一点办法也米有, 别人可以只为对象分配空间 而不初始化 ..
case05 枚举单例构造方法抛出异常
package com.hx.test04;
import sun.misc.Unsafe;
import java.io.*;
/**
* DoubleCheckSingleton
*
* @author Jerry.X.He <970655147@qq.com>
* @version 1.0
* @date 2020-04-01 17:01
*/
public enum Test22EnumConstructorExSingleton implements Serializable {
INSTANCE;
// disable constructor
private Test22EnumConstructorExSingleton() {
synchronized (BeanCreateFlagHolder.class) {
if(BeanCreateFlagHolder.INSTANCE_CREATED) {
throw new RuntimeException("can't instantiate !");
}
BeanCreateFlagHolder.INSTANCE_CREATED = true;
}
}
// Test18DoubleCheckSingleton
public static void main(String[] args) throws Exception {
Test22EnumConstructorExSingleton entity01 = Test22EnumConstructorExSingleton.getInstance();
// case1 constructor
// Cannot reflectively create enum objects
// Class<Test22EnumConstructorExSingleton> clazz = Test22EnumConstructorExSingleton.class;
// Constructor<Test22EnumConstructorExSingleton> constructor = clazz.getDeclaredConstructor(String.class, int.class);
//
// Method acquireConstructorAccessorMethod = Constructor.class.getDeclaredMethod("acquireConstructorAccessor");
// acquireConstructorAccessorMethod.setAccessible(true);
// acquireConstructorAccessorMethod.invoke(constructor);
//
// Field constructorAccessorField = Constructor.class.getDeclaredField("constructorAccessor");
// constructorAccessorField.setAccessible(true);
// ConstructorAccessor constructorAccessor = (ConstructorAccessor) constructorAccessorField.get(constructor);
// Test22EnumConstructorExSingleton entity02 = (Test22EnumConstructorExSingleton) constructorAccessor.newInstance(new Object[]{"xyz", 2});
// Test20EnumSingleton entity02 = constructor.newInstance("xyz", 2);
// case2 unsafe
Unsafe unsafe = Test18DoubleCheckSingleton.getUnsafe();
Test22EnumConstructorExSingleton entity03 = (Test22EnumConstructorExSingleton) unsafe.allocateInstance(Test22EnumConstructorExSingleton.class);
// case 3 deserialize
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(entity01);
byte[] serialized = baos.toByteArray();
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(serialized));
Test22EnumConstructorExSingleton entity04 = (Test22EnumConstructorExSingleton) ois.readObject();
int x = 1;
}
/**
* getInstance
*
* @return com.hx.test04.Test18DoubleCheckSingleton
* @author Jerry.X.He<970655147@qq.com>
* @date 2020-04-01 17:02
*/
public static Test22EnumConstructorExSingleton getInstance() {
return INSTANCE;
}
/**
* BeanCareatedHodler
*
* @author Jerry.X.He <970655147@qq.com>
* @version 1.0
* @date 2020-04-01 18:38
*/
static class BeanCreateFlagHolder {
// INSTANCE_CREATED
public static boolean INSTANCE_CREATED = false;
}
}
呵呵 这个就同上面一样了
unsafe 是一点办法也米有, 别人可以只为对象分配空间 而不初始化 ..
======================= add at 2020.04.25 =======================
case06 MagicAccessor
之前看到这篇帖子的时候, 突然想到了本文的问题 : java 反射调用 private 相关
呵呵 除了反射之外, 也可以使用 MagicAccessor 来 hack 调用 私有的构造方法嘛
package sun.reflect;
import com.hx.test04.Test18DoubleCheckSingleton;
import java.lang.reflect.InvocationTargetException;
/**
* MyMethodAccessor
*
* @author Jerry.X.He <970655147@qq.com>
* @version 1.0
* @date 2020-04-18 20:31
*/
public class MyMethodAccessor01 extends MethodAccessorImpl {
public MyMethodAccessor01() {
super();
}
@Override
public Object invoke(Object o, Object[] objects) throws IllegalArgumentException, InvocationTargetException {
new Test18DoubleCheckSingleton();
return null;
}
}
当然 不能直接这么写, 因为 compiler 检测到 Test18DoubleCheckSingleton 的构造方法不能访问, 但是 逻辑意义是这样
可以 通过一些方式来生成 这个 MethodAccessorImpl
1. 循环多次调用方法, 让 jdk 自己自动生成 MethodAccessorImpl
2. 通过相关字节码处理的 api 来生成一个这样的 class
3. 通过 ClassFileTransformer 来拦截 class, 然后修改 class
呵呵 不知道看完之后 您有何感想,
完
参考
42 不调用给定类的构造方法创建给定类的对象
java 反射调用 private 相关
最后
以上就是眯眯眼煎蛋为你收集整理的51 单例模式的几种hack前言case01 双检锁单例 case02 静态内部类 Holdercase03 枚举单例case04 双检锁单例构造方法抛出异常case05 枚举单例构造方法抛出异常case06 MagicAccessor 参考 的全部内容,希望文章能够帮你解决51 单例模式的几种hack前言case01 双检锁单例 case02 静态内部类 Holdercase03 枚举单例case04 双检锁单例构造方法抛出异常case05 枚举单例构造方法抛出异常case06 MagicAccessor 参考 所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复