枚举防止反射,克隆及序列化破环单例模式的原理
在上一篇文章中详细的介绍了实现单例模式的几种方式,以及介绍了通过反射,克隆及序列化方式对单例模式的破并给出了各自预防的对策。其中也指出了枚举是能够防止这三种方式对单例的破环。
首先我们都知道enum默认继承了 java.lang.Enum 类并实现了 java.lang.Seriablizable 和 java.lang.Comparable 两个接口。接下来我们将依次说明枚举是如何防止这三种方式对单例的破环
一、克隆
一个普通的类要是clone必须实现java.lang.Cloneable接口,重写clone()方法,同理我们来看看枚举能否也是一样
我们可以看到enum是不被允许重写clone(),因为Enum类已经将clone()方法定义为final了,并且Enum在使用clone()时直接抛出异常,如下图,这就是枚举为什么能防止克隆破环的原因,它根本就不允许克隆
二、反射
public class DestroySingleton { public static void main(String[] args) throws Exception { //通过反射获取 Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor(); constructor.setAccessible(true); Singleton reflex = constructor.newInstance(); System.out.println("reflex的hashCode:"+reflex.hashCode()); } }
我们先来看一下反射实现的主要步骤:首先通过class的getDeclaredConstructor()获取到反射对象的构造器,然后通过newInstance()调用其构造方法获取对象,getDeclaredConstructor()主要是通过getConstructor0()来获取构造器,具体代码如下:
@CallerSensitive public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) throws NoSuchMethodException, SecurityException { checkMemberAccess(Member.DECLARED, Reflection.getCallerClass(), true); return getConstructor0(parameterTypes, Member.DECLARED); }
在getConstructor0中,他会先调用privateGetDeclaredConstructors方法去获取;具体代码如下:
private Constructor<T> getConstructor0(Class<?>[] parameterTypes,int which) throws NoSuchMethodException { Constructor<T>[] constructors = privateGetDeclaredConstructors((which == Member.PUBLIC)); for (Constructor<T> constructor : constructors) { if (arrayContentsEq(parameterTypes,constructor.getParameterTypes())) { return getReflectionFactory().copyConstructor(constructor); } } throw new NoSuchMethodException(getName() + ".<init>" + argumentTypesToString(parameterTypes)); }
在privateGetDeclaredConstructors()中publicOnly的值是false,ReflectionData的publicConstructors和declaredConstructors都是null;而privateGetDeclaredConstructors()中真正决定Constructor<T>[]的代码是getDeclaredConstructors0(publicOnly)。
privateGetDeclaredConstructors具体代码如下:
private Constructor<T>[] privateGetDeclaredConstructors(boolean publicOnly) { checkInitted(); Constructor<T>[] res; ReflectionData<T> rd = reflectionData(); if (rd != null) { res = publicOnly ? rd.publicConstructors : rd.declaredConstructors; if (res != null) return res; } // No cached value available; request value from VM if (isInterface()) { @SuppressWarnings("unchecked") Constructor<T>[] temporaryRes = (Constructor<T>[]) new Constructor<?>[0]; res = temporaryRes; } else { res = getDeclaredConstructors0(publicOnly); } if (rd != null) { if (publicOnly) { rd.publicConstructors = res; } else { rd.declaredConstructors = res; } } return res; }
在得到Constructor<T>[]后回到getConstructor0()将依次对其进行轮询判断,找到合适的Constructor并交由ReflectionFactory工厂copy出一个Constructor。其中轮询的判断条件由parameterTypes和constructor.getParameterTypes()决定,parameterTypes是个空数组;普通类的constructor.getParameterTypes()得出的结果也是空数组,而枚举产生的数组为:[class java.lang.String, int];接着就交由arrayContentsEq()执行,并返回一个boolean值。
arrayContentsEq具体代码如下:
private static boolean arrayContentsEq(Object[] a1, Object[] a2) { if (a1 == null) { return a2 == null || a2.length == 0; } if (a2 == null) { return a1.length == 0; } if (a1.length != a2.length) { return false; } for (int i = 0; i < a1.length; i++) { if (a1[i] != a2[i]) { return false; } } return true; }
普通类在arrayContentsEq()中所有的if和for都通不过,最后直接返回true;而枚举类则会因为a1.length != a2.length(注:a2.length的之为2)条件成立而返回false。于是普通类接着执行return getReflectionFactory().copyConstructor(constructor);而枚举类则直接抛出异常throw new NoSuchMethodException(getName() + ".<init>" + argumentTypesToString(parameterTypes));具体错误信息如下:
Exception in thread "main" java.lang.NoSuchMethodException: designPatterns.singleton.useenum.Singleton.<init>()
at java.lang.Class.getConstructor0(Class.java:3082)
at java.lang.Class.getDeclaredConstructor(Class.java:2178)
at designPatterns.singleton.useenum.DestroySingleton.main(DestroySingleton.java:18)
从控制台输出的信息来看parameterTypes的确是一个空对象,但是为什么给出init()的NoSuchMethodException异常(这里我也百思不得其解有知道原因的朋友麻烦你告知一下^_^)。这就是为什么枚举不能通过反射实例化的原因之一,另一个原因就是:在获取到类构造器后通过newInstance()来实例化前,枚举是无法通过
if( (clazz.getModifiers() & Modifier.ENUM) != 0 )
条件判断的,具体代码如下:
@CallerSensitive 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; }
三、序列化
public class CreateClassBySerialized { @SuppressWarnings("unchecked") public static <T extends Serializable> T createClassBySerialized(T t) throws IOException, ClassNotFoundException{ ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(t); ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bis); T object = (T) ois.readObject(); if (ois != null) ois.close(); if (bis != null) bis.close(); if (oos != null) oos.close(); if (bos != null) bos.close(); return object; } } public class DestroySingleton { public static void main(String[] args) throws Exception { //通过序列化,反序列化获取 Singleton serialize = CreateClassBySerialized.createClassBySerialized(Singleton.getInstance()); System.out.println("serialize的hashCode:"+serialize.hashCode()); } }
首先我们先来看看为什么添加readResolve()方法就能防止序列化对单例的破环。关键的代码就是在readObject()里的readObject0()实现的,readObject()具体代码如下:
public final Object readObject() throws IOException, ClassNotFoundException{ if (enableOverride) { return readObjectOverride(); } // if nested read, passHandle contains handle of enclosing object int outerHandle = passHandle; try { Object obj = readObject0(false); handles.markDependency(outerHandle, passHandle); ClassNotFoundException ex = handles.lookupException(passHandle); if (ex != null) { throw ex; } if (depth == 0) { vlist.doCallbacks(); } return obj; } finally { passHandle = outerHandle; if (closed && depth == 0) { clear(); } } }
而readObject0()对类的实现体现在switch选择器上:
switch (tc) { case TC_NULL: return readNull(); case TC_REFERENCE: return readHandle(unshared); case TC_CLASS: return readClass(unshared); case TC_CLASSDESC: case TC_PROXYCLASSDESC: return readClassDesc(unshared); case TC_STRING: case TC_LONGSTRING: return checkResolve(readString(unshared)); case TC_ARRAY: return checkResolve(readArray(unshared)); case TC_ENUM: return checkResolve(readEnum(unshared)); case TC_OBJECT: return checkResolve(readOrdinaryObject(unshared)); case TC_EXCEPTION: IOException ex = readFatalException(); throw new WriteAbortedException("writing aborted", ex); case TC_BLOCKDATA: case TC_BLOCKDATALONG: if (oldMode) { bin.setBlockDataMode(true); bin.peek(); // force header read throw new OptionalDataException(bin.currentBlockRemaining()); } else { throw new StreamCorruptedException("unexpected block data"); } case TC_ENDBLOCKDATA: if (oldMode) { throw new OptionalDataException(true); } else { throw new StreamCorruptedException("unexpected end of block data"); } default: throw new StreamCorruptedException(String.format("invalid type code: %02X", tc)); }
tc值不同,类的实现方式也不同;普通类(TC_OBJECT)由readOrdinaryObject(unshared)来实现,枚举类(TC_ENUM)由readOrdinaryObject(unshared)来实现。
readOrdinaryObject(unshared)决定了类是通过构造器实现还是通过readResolve()来实现
关键代码如下:
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) { // Filter the replacement object if (rep != null) { if (rep.getClass().isArray()) { filterCheck(rep.getClass(), Array.getLength(rep)); } else { filterCheck(rep.getClass(), -1); } } handles.setObject(passHandle, obj = rep); } }
其中desc.hasReadResolveMethod()就是来用判断是否有readResolve()
boolean hasReadResolveMethod() { requireInitialized(); return (readResolveMethod != null); }
如果不存在readResolve()则readResolveMethod为null,反之则为readResolve()对应的Method对象(我这儿的是private java.lang.Object designPatterns.singleton.doublecheck.Singleton.readResolve() )。于是乎就执行desc.invokeReadResolve(obj)代码,通过Method.invoke执行readResolve()方法
Object invokeReadResolve(Object obj) throws IOException, UnsupportedOperationException { requireInitialized(); if (readResolveMethod != null) { try { return readResolveMethod.invoke(obj, (Object[]) null); } catch (InvocationTargetException ex) { Throwable th = ex.getTargetException(); if (th instanceof ObjectStreamException) { throw (ObjectStreamException) th; } else { throwMiscException(th); throw new InternalError(th); // never reached } } catch (IllegalAccessException ex) { // should not occur, as access checks have been suppressed throw new InternalError(ex); } } else { throw new UnsupportedOperationException(); } } @CallerSensitive public Object invoke(Object obj, Object... args) throws IllegalAccessException, IllegalArgumentException,InvocationTargetException { if (!override) { if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) { Class<?> caller = Reflection.getCallerClass(); checkAccess(caller, clazz, obj, modifiers); } } MethodAccessor ma = methodAccessor; // read volatile if (ma == null) { ma = acquireMethodAccessor(); } return ma.invoke(obj, args); // readResolve最终执行处
}
这样就得到了我们的静态singleton,实现单例模式。而在实现枚举的readEnum()方法中,枚举的实现是通过调用java.lang.Enum的静态方法valueOf来实现的
具体代码如下:
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); }
enumType.enumConstantDirectory()返回的对象是继承了枚举常量的hashMap,其中key键是枚举常量名字,value键是常量枚举对象本身;当它拿到枚举类中全部的枚举后,再其轮询将每一个枚举常量存入hashMap中:
Map<String, T> enumConstantDirectory() { if (enumConstantDirectory == null) { T[] universe = getEnumConstantsShared(); if (universe == null) throw new IllegalArgumentException( getName() + " is not an enum type"); Map<String, T> m = new HashMap<>(2 * universe.length); for (T constant : universe) m.put(((Enum<?>)constant).name(), constant); enumConstantDirectory = m; } return enumConstantDirectory; }
//getEnumConstantsShared()就是获取到的一个个枚举对象
T[] getEnumConstantsShared() {
if (enumConstants == null) {
if (!isEnum()) return null;
try {
final Method values = getMethod("values");
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction<Void>() {
public Void run() {
values.setAccessible(true);
return null;
}
});
@SuppressWarnings("unchecked")
T[] temporaryConstants = (T[])values.invoke(null);
enumConstants = temporaryConstants;
}
// These can happen when users concoct enum-like classes
// that don't comply with the enum spec.
catch (InvocationTargetException | NoSuchMethodException |
IllegalAccessException ex) { return null; }
}
return enumConstants;
}
当我们拿到枚举的hashMap后,通过get(name)方法获取对应的枚举然后层层返回。代码中实现枚举的入口代码是Enum.valueOf((Class)cl, name),这样实现的现过其实就是EnumClass.name(我代码的体现是Singleton.INSTANCE),这样来看的话无论是EnumClass.name获取对象,还是Enum.valueOf((Class)cl, name)获取对象,它们得到的都是同一个对象,这其实就是枚举保持单例的原理。
如有说错的地方请大家及时指出以免误导他人,谨诚拜谢