枚舉防止反射,克隆及序列化破環單例模式的原理


  在上一篇文章中詳細的介紹了實現單例模式的幾種方式,以及介紹了通過反射,克隆及序列化方式對單例模式的破並給出了各自預防的對策。其中也指出了枚舉是能夠防止這三種方式對單例的破環。

  首先我們都知道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)獲取對象,它們得到的都是同一個對象,這其實就是枚舉保持單例的原理。

 

    如有說錯的地方請大家及時指出以免誤導他人,謹誠拜謝

 

 

 

  

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM