一、枚举类方式创建单例模式举例
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;
}
}
注,文中内容源于视频中学习到的内容。