单例设计模式反射,序列化漏洞及解决方案

简介: 单例设计模式的实现方式有很多种,如饿汉式,懒汉式,双重检查锁,静态内部类,枚举等等,但是在平时的开发中,我们实现的单利模式是有一定的漏洞的,可以通过反射或者序列化以及反序列化获取不同的实例,虽然这个漏洞在系统运行的时候不会体现出来,但是在开发时也是值得注意的问题。

单例设计模式的实现方式有很多种,如饿汉式,懒汉式,双重检查锁,静态内部类,枚举等等,但是在平时的开发中,我们实现的单利模式是有一定的漏洞的,可以通过反射或者序列化以及反序列化获取不同的实例,虽然这个漏洞在系统运行的时候不会体现出来,但是在开发时也是值得注意的问题。

使用反射技术来获取不同的实例:

以下是一个简单的饿汉式的单利模式的代码实现:

package com.spring.designmodel;

import java.io.Serializable;

public class Singleton implements Serializable{

    private static final long serialVersionUID = 1L;

    private static final Singleton singleton = new Singleton();

    private Singleton(){}

    public static Singleton getInstance(){
        return singleton;
    }
}

当我们需要获取Singleton对象的时候,直接调用静态方法getInstance就可以了:

package com.spring.designmodel;

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


public class SingletonTest {

    public static void main(String[] args) {
        try {
            Singleton singleton1 = Singleton.getInstance();
            Singleton singleton2 = Singleton.getInstance();
            System.out.println(singleton1);
            System.out.println(singleton2);
            //使用反射获取对象实例
            Class<Singleton> clazz1 = (Class<Singleton>) Class.forName("com.spring.designmodel.Singleton");
            Constructor<Singleton> constructor1 = clazz1.getDeclaredConstructor(null);
            constructor1.setAccessible(true);
            Singleton singleton3 = constructor1.newInstance();

            Class<Singleton> clazz2 = (Class<Singleton>) Class.forName("com.spring.designmodel.Singleton");
            Constructor<Singleton> constructor2 = clazz2.getDeclaredConstructor(null);
            constructor2.setAccessible(true);
            Singleton singleton4 = constructor2.newInstance();
            System.out.println(singleton3);
            System.out.println(singleton4);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

但是学过反射的人都知道,通过反射技术也能获取到一个类的实例对象,即使它的构造函数时私有化的,我们也可以通过暴力访问来调用其构造函数,所以以上测试类的运行结果为:

com.spring.designmodel.Singleton@73fbaf73
com.spring.designmodel.Singleton@73fbaf73
com.spring.designmodel.Singleton@3f4f44
com.spring.designmodel.Singleton@3c6cf97c

可以看出通过调用getInstance方法获取到的实例是一样的,但是通过反射获取到的实例却是不同的,违反了单例设计模式的思想,那么我们应该怎么解决呢?我们只需要在私有的构造函数中加入一个判断即可:

package com.spring.designmodel;

import java.io.Serializable;

public class Singleton implements Serializable{

    private static final long serialVersionUID = 1L;

    private static final Singleton singleton = new Singleton();

    private Singleton(){
        if(null != singleton){
            throw new RuntimeException();
        }
    }

    public static Singleton getInstance(){
        return singleton;
    }
}

此时,我们再次启动测试类,获得到以下结果:

com.spring.designmodel.Singleton@73fbaf73
com.spring.designmodel.Singleton@73fbaf73
java.lang.reflect.InvocationTargetException
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:526)
at org.springsource.loaded.ri.ReflectiveInterceptor.jlrConstructorNewInstance(ReflectiveInterceptor.java:1075)
at com.spring.designmodel.SingletonTest.main(SingletonTest.java:19)
Caused by: java.lang.RuntimeException
at com.spring.designmodel.Singleton.(Singleton.java:13)
… 6 more

当然,就解决了使用反射技术来获取不同实例的问题了。

使用序列化及反序列化技术获取不同的实例:

如果我们的单例类实现了Serializable接口,那么这个类就能进行序列化和反序列化,测试代码如下:

package com.spring.designmodel;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;


public class SingletonTest {

    public static void main(String[] args){
        try {
            Singleton singleton1 = Singleton.getInstance();
            FileOutputStream fos = new FileOutputStream("d://singleton.txt");
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(Singleton.getInstance());

            FileInputStream fis = new FileInputStream("d://singleton.txt");
            ObjectInputStream ois = new ObjectInputStream(fis);
            Singleton singleton2 = (Singleton) ois.readObject();
            oos.close();
            ois.close();
            System.out.println(singleton1);
            System.out.println(singleton2);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

运行结果如下:
com.spring.designmodel.Singleton@139008b
com.spring.designmodel.Singleton@221c3dfe

我们发现进过序列化及反序列化之后对象的引用就改变了,显然也是违反了单例设计模式的思想的,跟踪readObject源码后,发现这个方法会先写出一个newInstance,然后判断这个对象中是否存在readResolve这个方法,如果不存在,那么直接返回这个newInstance,如果存在,那么就调用readResolve这个方法,将这个方法的返回值返回给readObject.源码片段如下:

    ObjectStreamClass desc = readClassDesc(false);
    desc.checkDeserialize();
    Class<?> cl = desc.forClass();
    if (cl == String.class || cl == Class.class
            || cl == ObjectStreamClass.class) {
        throw new InvalidClassException("invalid class descriptor");
    }

    Object obj;
    try {
        obj = desc.isInstantiable() ? desc.newInstance() : null;
    } catch (Exception ex) {
        throw (IOException) new InvalidClassException(
            desc.forClass().getName(),
            "unable to create instance").initCause(ex);
    }
    if (obj != null &&
    handles.lookupException(passHandle) == null &&
        desc.hasReadResolveMethod())
    {
        Object rep = desc.invokeReadResolve(obj);
        if (unshared && rep.getClass().isArray()) {
            rep = cloneArray(rep);
        }
        if (rep != obj) {
            handles.setObject(passHandle, obj = rep);
        }
    }

由上可知,我们只需要在Singleton这个类中添加一个readResolve这个方法即可。

package com.spring.designmodel;

import java.io.Serializable;

public class Singleton implements Serializable{

    private static final long serialVersionUID = 1L;

    private static final Singleton singleton = new Singleton();

    private Singleton(){
        if(null != singleton){
            throw new RuntimeException();
        }
    }

    public static Singleton getInstance(){
        return singleton;
    }

    public Object readResolve(){
        return singleton;
    }
}

再次启用测试类,运行结果如下:

com.spring.designmodel.Singleton@403729c5
com.spring.designmodel.Singleton@403729c5

目录
相关文章
|
6月前
|
设计模式
单例设计模式步骤
单例设计模式步骤
34 1
|
6月前
|
设计模式 安全 测试技术
【C/C++ 设计模式 单例】单例模式的选择策略:何时使用,何时避免
【C/C++ 设计模式 单例】单例模式的选择策略:何时使用,何时避免
140 0
|
6月前
|
JSON Java API
GSON 泛型对象反序列化解决方案
GSON 泛型对象反序列化解决方案
290 0
|
6月前
|
设计模式 安全 Java
在Java中即指单例设计模式
在Java中即指单例设计模式
40 0
|
2月前
|
设计模式 数据库连接 PHP
PHP中的设计模式:提升代码的可维护性与扩展性在软件开发过程中,设计模式是开发者们经常用到的工具之一。它们提供了经过验证的解决方案,可以帮助我们解决常见的软件设计问题。本文将介绍PHP中常用的设计模式,以及如何利用这些模式来提高代码的可维护性和扩展性。我们将从基础的设计模式入手,逐步深入到更复杂的应用场景。通过实际案例分析,读者可以更好地理解如何在PHP开发中应用这些设计模式,从而写出更加高效、灵活和易于维护的代码。
本文探讨了PHP中常用的设计模式及其在实际项目中的应用。内容涵盖设计模式的基本概念、分类和具体使用场景,重点介绍了单例模式、工厂模式和观察者模式等常见模式。通过具体的代码示例,展示了如何在PHP项目中有效利用设计模式来提升代码的可维护性和扩展性。文章还讨论了设计模式的选择原则和注意事项,帮助开发者在不同情境下做出最佳决策。
|
5月前
|
设计模式 SQL 安全
【设计模式】第二篇:单例模式的几种实现And反射对其的破坏
一个普通实例化,一个反射实例化 但是我们如果通过反射的方式进行实例化类,会有什么问题呢? public static void main(String[] args) throws Exception { Lazy1 lazy1 = getLazy1();
37 5
|
2月前
|
设计模式 存储 安全
设计模式——设计模式介绍和单例设计模式
饿汉式(静态常量)、饿汉式(静态代码块)、懒汉式(线程不安全)、懒汉式(线程安全,同步方法)、懒汉式(线程不安全,同步代码块)、双重检查(推荐,线程安全、懒加载)、静态内部类(推荐)、枚举(推荐)
设计模式——设计模式介绍和单例设计模式
|
3月前
|
设计模式 JavaScript 前端开发
从工厂到单例再到策略:Vue.js高效应用JavaScript设计模式
【8月更文挑战第30天】在现代Web开发中,结合使用JavaScript设计模式与框架如Vue.js能显著提升代码质量和项目的可维护性。本文探讨了常见JavaScript设计模式及其在Vue.js中的应用。通过具体示例介绍了工厂模式、单例模式和策略模式的应用场景及其实现方法。例如,工厂模式通过`NavFactory`根据用户角色动态创建不同的导航栏组件;单例模式则通过全局事件总线`eventBus`实现跨组件通信;策略模式用于处理不同的表单验证规则。这些设计模式的应用不仅提高了代码的复用性和灵活性,还增强了Vue应用的整体质量。
50 1
|
6月前
|
设计模式 安全 Java
【JAVA】Java 中什么叫单例设计模式?请用 Java 写出线程安全的单例模式
【JAVA】Java 中什么叫单例设计模式?请用 Java 写出线程安全的单例模式
|
3月前
|
设计模式 Java
【Java】单例设计模式
【Java】单例设计模式

热门文章

最新文章

  • 1
    C++一分钟之-设计模式:工厂模式与抽象工厂
    42
  • 2
    《手把手教你》系列基础篇(九十四)-java+ selenium自动化测试-框架设计基础-POM设计模式实现-下篇(详解教程)
    46
  • 3
    C++一分钟之-C++中的设计模式:单例模式
    54
  • 4
    《手把手教你》系列基础篇(九十三)-java+ selenium自动化测试-框架设计基础-POM设计模式实现-上篇(详解教程)
    38
  • 5
    《手把手教你》系列基础篇(九十二)-java+ selenium自动化测试-框架设计基础-POM设计模式简介(详解教程)
    62
  • 6
    Java面试题:结合设计模式与并发工具包实现高效缓存;多线程与内存管理优化实践;并发框架与设计模式在复杂系统中的应用
    57
  • 7
    Java面试题:设计模式在并发编程中的创新应用,Java内存管理与多线程工具类的综合应用,Java并发工具包与并发框架的创新应用
    41
  • 8
    Java面试题:如何使用设计模式优化多线程环境下的资源管理?Java内存模型与并发工具类的协同工作,描述ForkJoinPool的工作机制,并解释其在并行计算中的优势。如何根据任务特性调整线程池参数
    50
  • 9
    Java面试题:请列举三种常用的设计模式,并分别给出在Java中的应用场景?请分析Java内存管理中的主要问题,并提出相应的优化策略?请简述Java多线程编程中的常见问题,并给出解决方案
    106
  • 10
    Java面试题:设计模式如单例模式、工厂模式、观察者模式等在多线程环境下线程安全问题,Java内存模型定义了线程如何与内存交互,包括原子性、可见性、有序性,并发框架提供了更高层次的并发任务处理能力
    78