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

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

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

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

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

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

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

热门文章

最新文章