枚举实现单例模式可避免序列化和反射攻击

本文探讨了使用枚举类型实现单例模式的优势,包括其天然的序列化机制和防止反射攻击的能力。通过代码示例展示了枚举实现单例的过程,并通过序列化和反射测试验证了其稳定性和安全性。

  枚举类型天然的可序列化机制,能够强有力的保证不会出现多次实例化的情况,即使在复杂的序列化或者反射攻击的情况下,枚举类型的单例模式都没有问题。枚举类型的单例可能是实现单例模式中的最佳实践,《Effcetive Java》这本书也是强力推荐这种写法。

1、枚举实现单例模式
public enum EnumInstance {
    INSTANCE;
    private Object data;

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }

    public static EnumInstance getInstance(){
        return INSTANCE;
    }
}
2、序列化测试
    EnumInstance enumInstance = EnumInstance.getInstance();
    enumInstance.setData(new Object());
    ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton_file"));
    oos.writeObject(enumInstance);

    File file = new File("singleton_file");
    ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
    EnumInstance newEnumSingleton = (EnumInstance) ois.readObject();

    System.out.println(enumInstance.getData());
    System.out.println(newEnumSingleton.getData());
    System.out.println(enumInstance.getData()==newEnumSingleton.getData());

输出结果:
  java.lang.Object@2f4d3709
  java.lang.Object@2f4d3709
  true

  在ObjectInputStream类中有一个readEnum方法,通过readString方法获取到枚举对象的名称,再通过类型和名称来获取常量,因为枚举中的名称是唯一的,对应一个枚举常量,所以获取的常量一定是唯一的常量对象,这样就没有创建新的对象,维持了这个类的单例属性。

/**
     * Reads in and returns enum constant, or null if enum type is
     * unresolvable.  Sets passHandle to enum constant's assigned handle.
     */
    private Enum<?> readEnum(boolean unshared) throws IOException {
        // 校验部分代码已除去
        
        String name = readString(false);
        Enum<?> result = null;
        Class<?> cl = desc.forClass();
        if (cl != null) {
            try {
                @SuppressWarnings("unchecked")
                Enum<?> en = Enum.valueOf((Class)cl, name);
                result = en;
            } catch (IllegalArgumentException ex) {
                throw (IOException) new InvalidObjectException(
                    "enum constant " + name + " does not exist in " +
                    cl).initCause(ex);
            }
            if (!unshared) {
                handles.setObject(enumHandle, result);
            }
        }

        handles.finish(enumHandle);
        passHandle = enumHandle;
        return result;
    }
3、反射测试
    Class objectClass = EnumInstance.class;
    Constructor declaredConstructor = objectClass.getDeclaredConstructor(String.class, int.class);
    // 修改私有构造器的访问权限
    declaredConstructor.setAccessible(true);
    EnumInstance enumInstance = (EnumInstance) declaredConstructor.newInstance("singleton", 1);

  执行上述代码会抛出如下异常:java.lang.IllegalArgumentException: Cannot reflectively create enum objects,表示不同通过反射来创建枚举类的对象,在Constructor类中的newInstance()方法中有下列代码,当检测出类型为枚举类型,即抛出异常。

if ((clazz.getModifiers() & Modifier.ENUM) != 0){
     throw new IllegalArgumentException("Cannot reflectively create enum objects");
}
为防止 Java 单例模式反射序列化破坏,可采用以下方法: ### 防止反射破坏单例 在单例类的私有构造函数中添加检查逻辑,若实例已存在,则抛出异常,阻止通过反射创建新实例。示例代码如下: ```java public class BestLazyInnerClassSingleton { private BestLazyInnerClassSingleton() { if (LazyHolder.LAZY != null) { throw new RuntimeException("不允许创建多个实例"); } } public static final BestLazyInnerClassSingleton getInstance() { return LazyHolder.LAZY; } private static class LazyHolder { private static final BestLazyInnerClassSingleton LAZY = new BestLazyInnerClassSingleton(); } } ``` 此方式兼顾了饿汉式单例模式的内存浪费 `synchronized` 的性能问题,能有效防止反射破坏单例[^4]。 ### 防止序列化破坏单例 在单例类中重写 `readResolve()` 方法,使其返回单例实例,避免反序列化时创建新对象。示例代码如下: ```java import java.io.Serializable; public class Singleton implements Serializable { private static final long serialVersionUID = 1L; private static volatile Singleton instance; private Singleton() { if (instance != null) { throw new RuntimeException("请使用getInstance()方法获取实例"); } } public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } protected Object readResolve() { return getInstance(); } } ``` 通过重写 `readResolve()` 方法,可确保反序列化时返回的是原单例实例,防止序列化破坏单例[^2]。 ### 枚举方式 使用枚举实现单例模式能天然防止反射序列化破坏,示例代码如下: ```java enum Singleton { INSTANCE; public void doSomething() { // 具体业务逻辑 } } ``` 枚举方式在写法、线程安全,以及避免序列化反射攻击上都有优势[^2]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值