第五章 破坏单例模式

本文深入探讨了Java中的单例模式,包括简单工厂、工厂方法、抽象工厂模式以及单例模式的实现。特别地,文章详细分析了如何通过反射和序列化破坏单例模式的唯一性,并提供了防止此类破坏的方法。对于反射破坏,指出即使通过反射调用构造方法也能保持单例;对于序列化问题,解释了如何通过`readResolve()`方法防止单例在反序列化时被重新创建。文章最后总结了确保单例模式安全的重要性和实践技巧。

Java设计模式浅析

第一章 简单工厂模式
第二章 工厂方法模式
第三章 抽象工厂模式
第四章 单例模式
第五章 破坏单例模式


一、反射破坏单例

上一章中单例模式的构造方法除了加上 private 关键字,没有做任何处理。
如果我们使用反射来调用其构造方法,在调用 getInstance() 方法,
应该有两个不同的实例。现在来看一段测试代码,以 LazyInnerClassSingleton 为例:

示例代码

LazyInnerClassSingleton

public class LazyInnerClassSingleton {
    private LazyInnerClassSingleton() {
        /*if (LazyHolder.INSTANCE != null) {
            throw new RuntimeException("不允许创建多个实例");
        }*/
    }

    // 注意关键字final,保证方法不被重写和重载
    public static final LazyInnerClassSingleton getInstance() {
        // 在返回结果之前,会先加载内部类
        // 调用内部类属性
        return LazyHolder.INSTANCE;
    }

    // 默认不加载
    //LazyHolder  单例持有者
    // 最终返回 LazyHolder 的属性 LAZY 的引用,最终把引用返回到客户端
    private static class LazyHolder {
        private static final LazyInnerClassSingleton INSTANCE = new LazyInnerClassSingleton();
    }
}

 /*if (LazyHolder.INSTANCE != null) {
        throw new RuntimeException("不允许创建多个实例");
    }*/

测试代码

LazyInnerClassSingletonTest

public class LazyInnerClassSingletonTest {
    public static void main(String[] args) {
        
        try {
            // 声明一个Class对象 该对象是LazyInnerClassSingleton
            Class<?> clazz = LazyInnerClassSingleton.class;
            // 通过反射获取私有的构造方法
            Constructor c = clazz.getDeclaredConstructor(new Class[]{});
            // 强制访问  --private 私有成员变量 设置强制访问
            c.setAccessible(true);
            // 暴力初始化
            Object o1 = c.newInstance();
            // 调用了两次构造方法,相当于“new”了两次
            Object o2 = c.newInstance();
            System.out.println(o1 == o2);
            //System.out.println(o1);


        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
注释结果

在这里插入图片描述

非注释结果

在这里插入图片描述

二、序列化破坏单例

一个单例对象创建好后,有时候需要将对象序列化然后写入磁盘,
下次使用时再从磁盘中读取对象并进行反序列化,将其转化为内存对象。
反序列化后的对象会重新分配内存,即重新创建。如果序列化的目标对象为单例对象,
就违背了单例模式的初衷,相当于破坏了单例,来看一段代码:
示例代码

SeriableSingleton

public class SeriableSingleton implements Serializable {
    // 序列化就是把内存中的状态通过转换成字节码的形式
    // 从而转换一个 I/O 流,写入其他地方(可以是磁盘、网络 I/O)
    // 内存中的状态会永久保存下来

    // 反序列化就是将已经持久化的字节码内容转换为I/O流
    // 通过I/O流的读取,进而将读取的内容转换为Java对象
    // 在转换过程中会重新创建对象new
    private SeriableSingleton(){}
    private static final SeriableSingleton instance = new SeriableSingleton();
    public static SeriableSingleton getInstance(){
        return instance;
    }
      //避免序列化破坏单例
   /* private Object readResolve(){
        return instance;
    }*/
}

测试代码

SeriableSingletonTest

import java.io.*;


public class SeriableSingletonTest {
    public static void main(String[] args) {
        SeriableSingleton s1 = null;
        SeriableSingleton s2 = SeriableSingleton.getInstance();

        FileOutputStream fos = null;
        try {
            //文件输出流
            fos = new FileOutputStream("SeriableSingleton.obj");
            //对象输出流
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            //写对象
            oos.writeObject(s2);
            oos.flush();
            oos.close();

            FileInputStream fis = new FileInputStream("SeriableSingleton.obj");
            ObjectInputStream ois= new ObjectInputStream(fis);
            s1 = (SeriableSingleton) ois.readObject();
            ois.close();
            fis.close();

            System.out.println(s1 == s2);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
注释结果

在这里插入图片描述

非注释结果

在这里插入图片描述
//避免序列化破坏单例
/* private Object readResolve(){
return instance;
}*/

readResolve 详解见
https://www.yuque.com/yinjianwei/vyrvkf/uxggg3#lqkFI

总结

反射会创了多个实例,最后经过判断没有返回新的实例,创建多个实例但是并没有使用。
<think>嗯,用户想了解如何通过Java反射破坏单例模式的方法和原理。我需要先回顾单例模式的基本概念,然后看看反射是如何绕过其访问控制的。根据引用中的例子,比如引用[1]的代码,反射通过获取构造器并设置可访问性来实例化新对象。这可能是因为单例模式通常将构造器设为私有,但反射可以绕过这个限制。 然后要解释具体步骤,比如获取Class对象、获取构造器、设置setAccessible(true),然后调用newInstance。这样就能创建多个实例,破坏单例。原理上,反射机制允许在运行时访问和修改类信息,包括私有构造器,所以传统单例无法防御。 接着需要考虑如何防止这种破坏。引用[3]提到在构造器中加标志位,第二次调用时抛出异常。而引用[2]和[4]提到枚举和readResolve方法。比如枚举单例因为JVM保证其唯一性,反射无法实例化,所以更安全。此外,序列化破坏可以通过readResolve返回已有实例来避免。 用户可能想知道为什么枚举能防止反射破坏,根据引用[2],枚举类型的构造器在反射中被禁止调用,所以无法通过反射创建实例。而双重检查锁的单例容易被反射攻击,需要额外的防护措施。 需要确保回答结构清晰,分步骤说明破坏方法,再讲防御策略。同时引用相关的引用资料来支持每个点,比如引用[1]的代码示例,引用[3]的构造器处理,引用[2]的枚举解决方案。</think>### 通过反射破坏单例模式的实现方法及原理 #### 一、反射破坏单例的实现方法 1. **核心原理** 单例模式通过私有化构造函数限制外部直接实例化,但**反射机制可以绕过访问权限控制**,强制调用私有构造函数创建新实例[^1][^5]。以下是典型步骤: ```java // 获取单例类的Class对象 Class<SingleVo> clazz = SingleVo.class; // 获取私有构造器 Constructor<SingleVo> constructor = clazz.getDeclaredConstructor(); // 解除私有权限限制 constructor.setAccessible(true); //关键步骤 // 通过反射创建新实例 SingleVo instance = constructor.newInstance(); ``` 2. **破坏条件** 若单例实现未针对反射攻击进行防护(如枚举实现或构造函数二次调用检测),反射可重复创建不同实例[^3][^5]。 --- #### 二、防御反射攻击的方案 1. **构造函数二次调用检测** 在私有构造函数中添加状态标志,若检测到重复调用则抛出异常[^3]: ```java private static boolean flag = false; private Singleton() { if (flag) { throw new RuntimeException("禁止反射破坏单例"); } flag = true; } ``` 2. **枚举实现单例(最佳实践)** 枚举类型在JVM层面保证唯一性,且反射API会直接阻止通过构造器创建枚举实例[^2]: ```java public enum Singleton { INSTANCE; // 添加业务方法 public void doSomething() {} } ``` 3. **反序列化防护** 若单例类实现`Serializable`接口,需重写`readResolve()`方法返回已有实例,防止反序列化生成新对象[^4]: ```java protected Object readResolve() throws ObjectStreamException { return getInstance(); } ``` --- #### 三、原理对比 | 单例实现方式 | 反射破坏可能性 | 根本原因 | |-------------------|----------------|------------------------------| | 双重校验锁 | 高 | 反射可绕过私有构造器权限 | | 静态内部类 | 高 | 同双重校验锁 | | 枚举 | 低 | JVM禁止反射调用枚举构造器 | | 构造器二次检测 | 中 | 依赖代码逻辑防护 | ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值