

枚举防止反射,克隆及序列化破环单例模式的原理
source link: https://www.cnblogs.com/call-me-pengye/p/11214435.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

枚举防止反射,克隆及序列化破环单例模式的原理
在上一篇文章中详细的介绍了实现单例模式的几种方式,以及介绍了通过反射,克隆及序列化方式对单例模式的破并给出了各自预防的对策。其中也指出了枚举是能够防止这三种方式对单例的破环。
首先我们都知道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)获取对象,它们得到的都是同一个对象,这其实就是枚举保持单例的原理。
如有说错的地方请大家及时指出以免误导他人,谨诚拜谢
Recommend
-
65
#include<Psapi.h>#pragmacomment(lib,"Psapi.lib")DWORDdwProcesses[1024],dwRes;EnumProcesses(dwProcesses,sizeof(dwProcesses),&dwRes);for(inti=0;i<dwRes/sizeof(DWORD);i++){printf("%d%d\r\n",
-
72
常量 Golang里的constants就是constants,在编译时创建。常量可以是这几种: numbers, characters (runes), strings or booleans。因为是在编译时处理的,所以常量的值必须是编译器可以搞定的,比如1,”abc”,以及 len(“abc”)...
-
150
今天给大家介绍的是一款名叫Amass的深度子域名枚举工具,该工具采用Go语言开发,它可以通过遍历等形式爬取数据源和Web文档,或利用IP地址来搜索...
-
31
饿汉式 // 饿汉式单例 public class Hungry { //构造器私有 private Hungry(){ } // 一上来就把这个类加载了 private final static Hungry HUNGRY = new Hungry(); public static Hungry getI...
-
11
单例模式之枚举实现_刘伟技术博客-CSDN博客 如果你没有学过单例模式,请点击:确保对象的唯一性——单例模式。 有很多网友留言说我漏掉了一种非...
-
9
在 .NET 对象和 JSON 互相序列化的时候,枚举类型如何设置成字符串序列化,而不是整型? 2020-06-02 23:57 默认情况下,Newtonsoft.Json 库序列化和反序列化...
-
11
对序列化中反射的一点思考 - ksfzhaohui的个人页面 - OSCHINA - 中文开源技术交流社区 ...
-
12
单例模式的实现方式及如何有效防止防止反射和反序列化 方式一:饿汉式(静态常量)
-
4
Time'BlogJava反序列化-反射(二)发表于2022-01-02|更新于2022-03...
-
8
在Spring Boot中使用Java枚举自动序列化参数并自动保存数据库 分享在Spring应用程序中使用Java枚举
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK