情景
/**
* 枚举
* 实现简单,JVM从根本上提供保障,避免反射和反序列化的漏洞
* 无法延迟加载
* @author Administrator
*/
public enum SingletonDemo05 {
/**
* 枚举类对象
*/
INSTANCE;
private Object data;
public Object getData(){
return data;
}
public void setData(Object data){
this.data=data;
}
public static SingletonDemo05 getInstance(){
return INSTANCE;
}
}
尝试序列化破坏枚举单例
SingletonDemo05 o3=SingletonDemo05.getInstance();
FileOutputStream fos=new FileOutputStream("D:\\Object.txt");
ObjectOutputStream oos=new ObjectOutputStream(fos);
oos.writeObject(o3);
oos.close();
fos.close();
ObjectInputStream ois=new ObjectInputStream(new FileInputStream("D:\\Object.txt"));
Object o4=ois.readObject();
System.out.println(o3==o4);
序列化不会破坏枚举类的单例
尝试反射破坏枚举单例
Constructor c1 = SingletonDemo05.class.getDeclaredConstructor();
c1.setAccessible(true);
Object obj1=c1.newInstance();
Constructor c2 = SingletonDemo05.class.getDeclaredConstructor();
c2.setAccessible(true);
Object obj2=c1.newInstance();
System.out.println("创建的两个对象是否相同:"+(obj1==obj2));
枚举类单例禁止反射实例化
源码分析
序列化:
首先我们使用反编译工具jad反编译SingletonDemo05.class文件,生成SingletonDemo05.jad文件。可以看到代码中有一段:
static
{
INSTANCE = new SingletonDemo05("INSTANCE", 0);
$VALUES = (new SingletonDemo05[] {
INSTANCE
});
}
即枚举单例模式中,在静态代码块中给INSTANCE赋值,类似于饿汉式。即枚举类加载时,INSTANCE对象就实例化加载进内存了。那为什么序列化不会破坏单例呢。我们回到ObjectInputStream类的readObject0()
方法中,查看枚举类的相关方法
/**
* Underlying readObject implementation.
*/
private Object readObject0(boolean unshared) throws IOException {
···
case TC_ENUM:
return checkResolve(readEnum(unshared));
···
}
进入readEnum(boolean unshared)
方法
/**
* 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;
}
枚举类的赋值
Enum<?> en = Enum.valueOf((Class)cl, name);
public static <T extends Enum<T>> T valueOf(Class<T> enumType,
String name) {
T result = enumType.enumConstantDirectory().get(name);
if (result != null)
return result;
if (name == null)
throw new NullPointerException("Name is null");
throw new IllegalArgumentException(
"No enum constant " + enumType.getCanonicalName() + "." + name);
}
枚举类型通过类名和类对象类只能找到唯一的一个枚举对象,因为枚举类在类加载式实例化了INSTANCE枚举类,那么在内存中根据类名和类对象类也就只能找到这一个INSTANCE枚举类对象,也就保证了枚举类的单例。
反射:
关于反射我们直接查看报错和Enum源码
报错行
Constructor c1 = SingletonDemo05.class.getDeclaredConstructor();
即没有找到无参的构造方法,接着我们查看Enum源码
protected Enum(String name, int ordinal) {
this.name = name;
this.ordinal = ordinal;
}
只有一个protected修饰的有参构造方法。我们根据他的有参方法修改测试类
Constructor c1 = SingletonDemo05.class.getDeclaredConstructor(String.class,int.class);
c1.setAccessible(true);
Object obj1=c1.newInstance("xxbb",1);
运行一下
报错行
Object obj1=c1.newInstance("xxbb",1);
这里就很直接告诉你不能通过反射实例化枚举对象,我们查看下newInstance()
方法的源码
public T newInstance(Object ... initargs)
throws InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException
{
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, null, modifiers);
}
}
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");
ConstructorAccessor ca = constructorAccessor; // read volatile
if (ca == null) {
ca = acquireConstructorAccessor();
}
@SuppressWarnings("unchecked")
T inst = (T) ca.newInstance(initargs);
return inst;
}
可以看到在方法中直接进行了判断,如果修饰符是Modifier.ENUM
就抛出异常,也就是说在jdk1.8中不允许通过反射实例化枚举类对象。