单例模式指的是一个类只有一个对象,通过一些措施达到达到这个目的。但是反射和反序列化可以获得多个不同的对象。


  先简单的认识一下单例模式

一:单例模式

  通过私有构造器,声明一个该类的静态对象成员,提供一个获得对象的静态方法实现单例模式。单列模式有饿汉式和懒汉式,饿汉式是声明的同时就为该对象赋值。

懒汉式指的是使用到的时候再创建。虚拟机的实现会保证:类加载会确保类和对象的初始化方法在多线程场景下能够正确的同步加锁,即饿汉式声明并赋值是原子操作,不会存在同步问题。懒汉式为了应付同步问题出现了加锁,内部类延时加载等。

  单例模式博客地址:http://www.cnblogs.com/tfper/p/9890971.html

二:单例模式存在的漏洞

  1.反射破解单例模式

    java的访问控制是停留在编译层的,也就是它不在在class文件中保留下任何痕迹,只在编译的时候进行访问控制的检查。通过反射的手段可以访问类中的成员,比如私有构造方法。

    我们先写一个简单的懒汉式的类:

package cnblogs.bean;

public class Singleton {
private static Singleton sl; //懒汉式 private Singleton() {} public static Singleton getInstance() {
if(sl == null)
sl = new Singleton();
return sl;
}
}

通过反射获取单例对象。

package cnblogs.test;

import java.lang.reflect.Constructor;

import cnblogs.bean.Singleton;

public class Test2 {

	@SuppressWarnings("unchecked")
public static void main(String[] args) throws Exception {
//正常获得单例模式对象
Singleton s1 = Singleton.getInstance();
System.out.println(s1); //通过反射获得单例模式对象
Class<Singleton> cl = (Class<Singleton>)
Class.forName("cnblogs.bean.Singleton");
Constructor<Singleton> constructor = cl.getDeclaredConstructor();
constructor.setAccessible(true);
Singleton s2 = constructor.newInstance(); System.out.println(s2);
System.out.println("s1 == s2? " + (s1 == s2));
} }

控制台输出:

  cnblogs.bean.Singleton@6d06d69c
  cnblogs.bean.Singleton@7852e922
   s1 == s2? false

观察一下就可以发现两种方式获得的单例模式不一样,违反了单例模式。

破解方式:

  我们观察反射获取单例的代码,发现它还是调用了私有的构造方法获取对象【声明为私有的构造方法就是为了不让类外直接new对象】。如果只让私有的构造器只能调用一次就可以避免反射。

package cnblogs.bean;

public class Singleton {
private static Singleton sl; private Singleton() {
//如果sl不为空即这不是第一次调用该构造器
if(sl != null)
throw new RuntimeException();
} public static Singleton getInstance() {
if(sl == null) {
sl = new Singleton();
}
return sl;
}
}

再次调用上面的反射测试类看控制台输出:

cnblogs.bean.Singleton@6d06d69c
Exception in thread "main" java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(Unknown Source)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(Unknown Source)
    at java.lang.reflect.Constructor.newInstance(Unknown Source)
    at cnblogs.test.Test2.main(Test2.java:20)
Caused by: java.lang.RuntimeException
    at cnblogs.bean.Singleton.<init>(Singleton.java:10)
    ... 5 more

三:反序列化获得单例模式对象

  单例类:

package cnblogs.bean;

import java.io.Serializable;
/**
* 序列化必须实现Serializable接口,否则序列化时会报错
*
*/
public class Singleton implements Serializable{ private static final long serialVersionUID = 1L; private static Singleton sl; private Singleton() {
//如果sl不为空即这不是第一次调用该构造器
if(sl != null)
throw new RuntimeException();
} public static Singleton getInstance() {
if(sl == null) {
sl = new Singleton();
}
return sl;
}
}

  测试类:

package cnblogs.test;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream; import cnblogs.bean.Singleton; public class Test3 { public static void main(String[] args) {
Singleton s1 = Singleton.getInstance();
System.out.println(s1);
//先序列化后反序列化
try {
ByteArrayOutputStream os = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(os);
oos.writeObject(s1); InputStream is = new ByteArrayInputStream(os.toByteArray());
ObjectInputStream ois = new ObjectInputStream(is);
Singleton s2 = (Singleton) ois.readObject(); System.out.println(s2);
System.out.println("s1 == s2? " + (s1 == s2));
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} } }

控制台输出:

  cnblogs.bean.Singleton@6d06d69c
  cnblogs.bean.Singleton@42a57993
  s1 == s2? false

解决方法:

  

package cnblogs.bean;

import java.io.Serializable;
/**
* 序列化必须实现Serializable接口,否则序列化时会报错
*
*/
public class Singleton implements Serializable{ private static final long serialVersionUID = 1L; private static Singleton sl; private Singleton() {
//如果sl不为空即这不是第一次调用该构造器
if(sl != null)
throw new RuntimeException();
} public static Singleton getInstance() {
if(sl == null) {
sl = new Singleton();
}
return sl;
} /**
* 反序列化时,如果定义了readResolve()则直接返回此方法指定的对象,而不需要在创建新的对象!
* @return
*/
private Object readResolve() {
return getInstance();
}
}

反序列化时,如果定义了readResolve()则直接返回此方法指定的对象,而不需要在创建新的对象!

运行效果:

  cnblogs.bean.Singleton@6d06d69c
  cnblogs.bean.Singleton@6d06d69c
  s1 == s2? true


 

java基础(三):反射、反序列化破解单列模式和解决方式的更多相关文章

  1. java基础语法(二)--单列模式

    java基础语法(二)--单列模式 /** * 功能:单列模式 * @author Administrator * */ public class SingletonTest { public sta ...

  2. 黑马程序员:Java基础总结----反射

    黑马程序员:Java基础总结 反射   ASP.Net+Android+IO开发 . .Net培训 .期待与您交流! 反射 反射的基石:Class类 Class类代表Java类,它的各个实例对象又分别 ...

  3. Python 基础 三 反射

    Python 基础 三 反射 今天我们先介绍一下反射这个概念,啥是反射?反射就是自己检测自己.在我们Python的面向对象中的反射是啥意思呢?就是通过字符串的形式操作对象相关的属性.python中的一 ...

  4. Java基础之一反射

    反射是框架设计的灵魂 (使用的前提条件:必须先得到代表的字节码的Class,Class类用于表示.class文件(字节码))   一.反射的概述 JAVA反射机制是在运行状态中,对于任意一个类,都能够 ...

  5. Java基础之—反射

    反射是框架设计的灵魂 (使用的前提条件:必须先得到代表的字节码的Class,Class类用于表示.class文件(字节码))   一.反射的概述 JAVA反射机制是在运行状态中,对于任意一个类,都能够 ...

  6. Java基础—序列化与反序列化(转载)

    转载自: Java序列化与反序列化 1.Java序列化与反序列化 Java序列化是指把Java对象转换为字节序列的过程:而Java反序列化是指把字节序列恢复为Java对象的过程. 2.为什么需要序列化 ...

  7. JAVA基础知识|反射

    一.理解反射 1.1.基础概念 反射:在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法:对于任意一个对象,都能够调用它的任意方法和属性:这种动态获取信息以及动态调用对象方法的功能称为ja ...

  8. 在JAVA和android中常用的单列模式

    在很多开发中,项目为了节约资源,都把一个类的构造函数变为私有化,这样整个项目中就不能创建多个实例,这样的方法我们称为单例模式 现在通过代码来简介下这个单例模式: 在新建一个java项目后,创建一个实体 ...

  9. java基础之反射机制

    一.概念 JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法:对于任意一个对象,都能够调用它的任意一个方法和属性:这种动态获取的信息以及动态调用对象的方法的功能称为jav ...

随机推荐

  1. ESP32搭建4.esp32官方程序下载与串口读取

    上一篇写到esp-idf的整体环境搭建,本文主要讲讲如何将examples中的例子烧写到ESP32的flash中,以及如何通过linux串口进行调试. 一. Esp32程序下载 1.Ctrl+Alt+ ...

  2. angularjs i18n

    <!doctype html><html ng-app="myApp"><head>    <meta charset="utf ...

  3. 三元运算和bytes数据类型笔记

    三元运算 result = 值1 if条件  else 值2 如果条件为真:result = 值1 如果条件为假:result = 值2 bytes数据类型 字符串可以编码成字节包,字节包也可以解码成 ...

  4. unity重写软键盘for Android NGUI

    一 原因 以为某些原因,需要重写Unity游戏中Android的软键盘的样式,然而unity对android和ios的ios的软键盘都有封装,在Unity中TouchScreenKeyboard.Op ...

  5. 1 实现添加功能 1.1 定义一个学员类(Student),在Student类中定义姓名、性别和年龄属性,定义有 参数的构造方法来初始化所以的成员属性 1.2 创建学员类对象来存放学员信息,并且为每一个学生对象添加的相应的编号。并将 学员类对象添加到Map<Integer,Student>集合中 1.3 添加完成后,显示所有已添加的学员姓名 1.4 限制年龄文本框只能输入正整数,否则的会采

    学生类 package com.lanxi.demo1_3; public class Student { private String name; private String sex; priva ...

  6. eclipse 中修改项目名称,启动tomct原项目找不到的问题

    eclipse 中修改项目名称,启动tomct原项目找不到的问题 1 在开发和练习中,经常会导入其他开源项目,修改项目名称,部署到tomcat 后,然后访问项目会找不到项目,可能是新项目名称和配置文件 ...

  7. WebGl配置

    <configuration> <system.webServer> <staticContent> <remove fileExtension=" ...

  8. OpenGL之纹理贴图(Texture)

    学习自: https://learnopengl-cn.github.io/01%20Getting%20started/06%20Textures/ 先上一波效果图: 实际上就是:画了一个矩形,然后 ...

  9. [RESTful] RESTful是什么,为什么要使用它

    RESTful是什么? 本质:一种软件架构风格 核心:面向资源 解决的问题:降低开发的复杂性,提高系统的可伸缩性 设计概念和准则: 1.网络上所有的事物都可以被抽象为资源 2.每个资源都有唯一的资源标 ...

  10. swoole架构分析

    swoole的进程/线程结构 结构图如下: swoole主要由Master进程(主进程)和Manager进程配合使用完成其功能. Master进程 是一个多线程的程序.其中有一组很重要的线程,称之为R ...