单例设计模式之枚举类型实现单例设计模式的原理源码和反射攻击防御

一、枚举类方式创建单例模式举例

package com.zxl.design.zxl.design.pattern.singleton;

import java.io.ObjectInputStream;

/**
 * Created by Administrator on 2019/6/30.
 */
public enum EnumInstance {
    INSTANCE;
    private Object data;

    public Object getData() {
        return data;
    }
    public void setData(Object data) {
        this.data = data;
    }
    public static EnumInstance getEnumInstance(){
        return INSTANCE;
    }
}

二、分析

因为枚举类天然的序列化和反序列化机制、多线程实例唯一的特质,以及防反射攻击的特性,从而之前的问题都不存在,所以,枚举类型可能是最佳的单例模式设计实践,强烈推荐。首先验证下:

package com.zxl.design.zxl.design.pattern.singleton;

import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

/**
 * Created by Administrator on 2019/6/16.
 */
public class Test {
    public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {

      EnumInstance enumInstance = EnumInstance.getEnumInstance();
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("single_file"));
        oos.writeObject(enumInstance);
        ObjectInputStream fis = new ObjectInputStream(new FileInputStream(new File("single_file")));

        EnumInstance newEnumInstance = (EnumInstance) fis.readObject();
        System.out.println(enumInstance);
        System.out.println(newEnumInstance);
        System.out.println(enumInstance == newEnumInstance);

}
"D:\Program Files\Java\jdk1.8.0_102\bin\java" -agentlib:jdwp=transport=dt_socket,address=127.0.0.1:58218,suspend=y,server=n -Dfile.encoding=UTF-8 -classpath "D:\Program Files\Java\jdk1.8.0_102\jre\lib\charsets.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\deploy.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\access-bridge-64.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\cldrdata.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\dnsns.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\jaccess.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\jfxrt.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\localedata.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\nashorn.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\sunec.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\sunjce_provider.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\sunmscapi.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\sunpkcs11.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\zipfs.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\javaws.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\jce.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\jfr.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\jfxswt.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\jsse.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\management-agent.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\plugin.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\resources.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\rt.jar;C:\Users\Administrator\Desktop\gson-master\gson-master\DesignMode\target\classes;D:\InteliijIDea\IntelliJ IDEA 2017.1.4\lib\idea_rt.jar" com.zxl.design.zxl.design.pattern.singleton.Test
Connected to the target VM, address: '127.0.0.1:58218', transport: 'socket'
INSTANCE
INSTANCE
true
Disconnected from the target VM, address: '127.0.0.1:58218', transport: 'socket'

上面只是对一个基本的属性使用了一下,因为我们主要关心的是里面持有的data对象,所以,尝试为data赋值一个对象,经过序列化和反序列化,确认下对象的属性如何。

package com.zxl.design.zxl.design.pattern.singleton;

import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

/**
 * Created by Administrator on 2019/6/16.
 */
public class Test {
    public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {

      EnumInstance enumInstance = EnumInstance.getEnumInstance();
        //注意这里
      enumInstance.setData(new Object());
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("single_file"));
        oos.writeObject(enumInstance);
        ObjectInputStream fis = new ObjectInputStream(new FileInputStream(new File("single_file")));

        EnumInstance newEnumInstance = (EnumInstance) fis.readObject();
//还有这里哦
        System.out.println(enumInstance.getData());
        System.out.println(newEnumInstance.getData());
        System.out.println(enumInstance.getData() == newEnumInstance.getData());

    }
}
"D:\Program Files\Java\jdk1.8.0_102\bin\java" "-javaagent:D:\InteliijIDea\IntelliJ IDEA 2017.1.4\lib\idea_rt.jar=58272:D:\InteliijIDea\IntelliJ IDEA 2017.1.4\bin" -Dfile.encoding=UTF-8 -classpath "D:\Program Files\Java\jdk1.8.0_102\jre\lib\charsets.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\deploy.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\access-bridge-64.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\cldrdata.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\dnsns.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\jaccess.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\jfxrt.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\localedata.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\nashorn.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\sunec.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\sunjce_provider.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\sunmscapi.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\sunpkcs11.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\zipfs.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\javaws.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\jce.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\jfr.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\jfxswt.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\jsse.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\management-agent.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\plugin.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\resources.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\rt.jar;C:\Users\Administrator\Desktop\gson-master\gson-master\DesignMode\target\classes" com.zxl.design.zxl.design.pattern.singleton.Test
java.lang.Object@7ba4f24f
java.lang.Object@7ba4f24f
true

Process finished with exit code 0

为什么呢?分析下

在ObjectInputStream 这个类中,有关于readObject方法,和再次进入readObject0()方法中,其中

case TC_ENUM:
    return checkResolve(readEnum(unshared));
其中进入readEnum()方法,如下是比较关键地方,
 /**
     * 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 {
        if (bin.readByte() != TC_ENUM) {
            throw new InternalError();
        }

        ObjectStreamClass desc = readClassDesc(false);
        if (!desc.isEnum()) {
            throw new InvalidClassException("non-enum class: " + desc);
        }

        int enumHandle = handles.assign(unshared ? unsharedMarker : null);
        ClassNotFoundException resolveEx = desc.getResolveException();
        if (resolveEx != null) {
            handles.markException(enumHandle, resolveEx);
        }

        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;
    }
其中,实现序列化反序列化唯一的关键在这个地方,大家可以自行理解下
 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) {

第二项,反射攻击

package com.zxl.design.zxl.design.pattern.singleton;

import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

/**
 * Created by Administrator on 2019/6/16.
 */
public class Test {
    public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {

 //通过反射构造器类创建对象的方式进行创建
        Class objectClass = EnumInstance.class;
        Constructor constructor = objectClass.getDeclaredConstructor();
//        增加如下方法,看能否正常运行
        constructor.setAccessible(true);
//        如上可能会爆红,直接简单抛出异常(注意我们这里简单起见,真实业务场景要具体分析)
//        通过正常单例模式构建对象的方式进行创建
        EnumInstance newEnumSingleton = (EnumInstance) constructor.newInstance();
        EnumInstance enumInstance = EnumInstance.getEnumInstance();
        System.out.println(enumInstance);
        System.out.println(newEnumSingleton);
        System.out.println(enumInstance == newEnumSingleton);
    }
}

 

Exception in thread "main" java.lang.NoSuchMethodException: com.zxl.design.zxl.design.pattern.singleton.EnumInstance.<init>()
	at java.lang.Class.getConstructor0(Class.java:3082)
	at java.lang.Class.getDeclaredConstructor(Class.java:2178)
	at com.zxl.design.zxl.design.pattern.singleton.Test.main(Test.java:41)

Process finished with exit code 1

 如上也就说明,该类成功避开了反射攻

如何来分析如上的反射攻击的原理呢?

在此,根据学习到的内容,可以通过反编译的方式去了解为何会实现如此功效。推荐的反编译工具就是jad

可以下载jad安装包,安装完成,通过如下方式,对java编译过的代码进行反编译,查看其中原理,如果还没安装上,那具体使用方法为:

1、可以在官网下载安装包https://varaneckas.com/jad/

2、安装完成,可以为该工具配置全局环境变量,在此将以在Linux环境为例讲解,如果是Windows等其他,可以另了解使用方式

查看jad工具位置:ll grep | jad

为该工具配置环境变量 vim  .bash_profile

3、配置完成可以使用,首先找到打算进行反编译的路径

 4、来到命令行,进行使用

 jad 加要反编译的.class文件就可以查看结果了

 如上就能直接看到反编译出的结果了。

而从反编译的结果可以看到,这里面的部分实现和饿汉式模式非常相近。

三、关于枚举类的相关使用,简单扩展其使用,比如枚举类中可以定义方法。

package com.zxl.design.zxl.design.pattern.singleton;

import java.io.ObjectInputStream;

/**
 * Created by Administrator on 2019/6/30.
 */
public enum EnumInstance {
    INSTANCE{
        protected void printTest(){
            System.out.println("枚举类型的方法");
        }
    };
//只有在此写了这样的方法,外面才能访问到printTest()方法
    protected abstract void printTest();
    private Object data;

    public Object getData() {
        return data;
    }
    public void setData(Object data) {
        this.data = data;
    }
    public static EnumInstance getEnumInstance(){
        return INSTANCE;
    }
}

注,文中内容源于视频中学习到的内容。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值