JAVA反射原理


什么是反射?

反射,一種計算機處理方式。是程序可以訪問、檢測和修改它本身狀態或行為的一種能力。java反射使得我們可以在程序運行時動態加載一個類,動態獲取類的基本信息和定義的方法,構造函數,域等。除了檢閱類信息外,還可以動態創建類的實例,執行類實例的方法,獲取類實例的域值。反射使java這種靜態語言有了動態的特性。

類的加載

java反射機制是圍繞Class類展開的,在深入java反射原理之前,需要對類加載機制有一個大致的了解。jvm使用ClassLoader將字節碼文件(class文件)加載到方法區內存中:

Class clazz = ClassLoader.getSystemClassLoader().loadClass("com.mypackage.MyClass");

可見ClassLoader根據類的完全限定名加載類並返回了一個Class對象,而java反射的所有起源都是從這個class類開始的。

ReflectionData

為了提高反射的性能,緩存顯然是必須的。class類內部有一個useCaches靜態變量來標記是否使用緩存,這個值可以通過外部配置項sun.reflect.noCaches進行開關。class類內部提供了一個ReflectionData內部類用來存放反射數據的緩存,並聲明了一個reflectionData域,由於稍后進行按需延遲加載並緩存,所以這個域並沒有指向一個實例化的ReflectionData對象。

    //標記是否使用緩存,可以通過外部配置項sun.reflect.noCaches進行禁用。
    private static boolean useCaches = true;

    static class ReflectionData<T> {
        volatile Field[] declaredFields;
        volatile Field[] publicFields;
        volatile Method[] declaredMethods;
        volatile Method[] publicMethods;
        volatile Constructor<T>[] declaredConstructors;
        volatile Constructor<T>[] publicConstructors;
        volatile Field[] declaredPublicFields;
        volatile Method[] declaredPublicMethods;
        final int redefinedCount;

        ReflectionData(int redefinedCount) {
            this.redefinedCount = redefinedCount;
        }
    }

    //注意這是個SoftReference,在內存資源緊張的時候可能會被回收。volatile保證多線程環境下的讀寫的正確性
    private volatile transient SoftReference<ReflectionData<T>> reflectionData;
    //J主要用於和ReflectionData中的redefinedCount進行比較,如果兩個值不相等,說明ReflectionData緩存的數據已經過期了。
    private volatile transient int classRedefinedCount = 0;

獲取類的構造函數

在獲取一個類的class后,我們可以通過反射獲取一個類的所有構造函數,class類內部封裝了如下方法用於提取構造函數:

    //publicOnly指示是否只獲取public的構造函數,我們常用的getConstructors方法是只返回public的構造函數,而getDeclaredConstructors返回的是所有構造函數,由於java的構造函數不會繼承,所以這里不包含父類的構造函數。
    private Constructor<T>[] privateGetDeclaredConstructors(boolean publicOnly) {
        checkInitted(); //主要是讀取了sun.reflect.noCaches配置。
        Constructor<T>[] res;
        ReflectionData<T> rd = reflectionData();//這里緩存中讀取reflectionData,如果還沒有緩存,則創建一個reflectionData並設置到緩存。但是注意這個ReflectionData可能只是個空對象,里面並沒有任何數據。
        if (rd != null) {
            res = publicOnly ? rd.publicConstructors : rd.declaredConstructors;
            if (res != null) return res;//檢查緩存中是否有數據
        }
        //沒有緩存數據可用,從這里開始需要從jvm中去獲取數據。
        if (isInterface()) {
            res = new Constructor[0];//接口沒有構造函數
        } else {
            res = getDeclaredConstructors0(publicOnly);//native方法,從jvm中獲取
        }
        //如果代碼執行到了這里,說明需要更新緩存了,將之前從jvm中請求到的數據放置到緩存中。
        if (rd != null) {
            if (publicOnly) {
                rd.publicConstructors = res;
            } else {
                rd.declaredConstructors = res;
            }
        }
        return res;
    }

上面的代碼片段比較簡單,主要就是讀取配置,檢查緩存中是否有有效數據,如果有,直接從緩存中返回,如果沒有,調用native的方法從jvm中請求數據,然后設置到緩存。比較重要的是reflectionData()這個調用。這個方法主要是用於延遲創建並緩存ReflectionData對象,注意是對象,里面並沒有保存反射數據,這些數據只有在第一次執行相應的反射操作后才會被填充。下面是這個方法的實現代碼:

    private ReflectionData<T> reflectionData() {
        SoftReference<ReflectionData<T>> reflectionData = this.reflectionData;
        int classRedefinedCount = this.classRedefinedCount;
        ReflectionData<T> rd;
        //檢查緩存是否有效,如果有效,從緩存中直接返回reflectionData。
        if (useCaches &&
                reflectionData != null &&
                (rd = reflectionData.get()) != null &&
                rd.redefinedCount == classRedefinedCount) {
            return rd;
        }
        //無法使用到緩存,創建新的reflectionData
        return newReflectionData(reflectionData, classRedefinedCount);
    }

下面看一下newReflectionData()的實現:

    private ReflectionData<T> newReflectionData(SoftReference<ReflectionData<T>> oldReflectionData,int classRedefinedCount) {
        if (!useCaches) return null;
        //這里使用while-cas模式更新reflectionData,主要是考慮多線程並發更新的問題,可能有另外一個線程已經更新了reflectionData,並且設置了有效的緩存數據,如果這里再次更新就把緩存數據覆蓋了。
        while (true) {
            ReflectionData<T> rd = new ReflectionData<>(classRedefinedCount);
            if (Atomic.casReflectionData(this, oldReflectionData, new SoftReference<>(rd))) {
                return rd;//cas成功,那么我們swap的這個新對象是有效的。
            }
            //重新讀取oldReflectionData,為下次重試cas做准備。
            oldReflectionData = this.reflectionData;
            classRedefinedCount = this.classRedefinedCount;
            //先判斷這個oldReflectionData是否有效。如果是無效的數據,需要去重試cas一個新的ReflectionData了。
            if (oldReflectionData != null &&
                    (rd = oldReflectionData.get()) != null &&
                    rd.redefinedCount == classRedefinedCount) {
                return rd;//reflectionData中已經是有效的緩存數據了,直接返回這個reflectionData
            }
        }
    }

上面的幾個代碼片段是反射獲取一個類的構造函數的主要方法調用。主要流程就是先從class內部的reflectionData緩存中讀取數據,如果沒有緩存數據,那么就從jvm中去請求數據,然后設置到緩存中,供下次使用。

執行構造函數

在通過反射獲取到一個類的構造函數我們,一般我們會試圖通過構造函數去實例化聲明這個構造函數的類,如下:

MyClass myClass = (MyClass) constructor.newInstance();

下面來看看這個newInstance()到底發生了什么:

    public T newInstance(Object... initargs)
            throws InstantiationException, IllegalAccessException,
            IllegalArgumentException, InvocationTargetException {
        //首先判斷語言級別的訪問檢查是否被覆蓋了(通過setAccess(true)方法可以將private的成員變成public),如果沒有被覆蓋,需要進行訪問權限檢查。
        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對象,並進行緩存
        ConstructorAccessor ca = constructorAccessor;
        if (ca == null) {
            ca = acquireConstructorAccessor();
        }
    //通過ConstructorAccessor來執行newInstance
        return (T) ca.newInstance(initargs);
    }

上面代碼主要就是做權限檢查,如果權限通過,則通過執行acquireConstructorAccessor()獲取一個ConstructorAccessor來真正執行newInstance,acquireConstructorAccessor()這個方法實現比較簡單,真正工作的是ReflectionFactory.newConstructorAccessor(),這個方法的主要工作就是為一個Constructor動態生成針對ConstructorAccessor接口的實現ConstructorAccessorImpl,下面來看一下這個方法實現:

    public ConstructorAccessor newConstructorAccessor(Constructor c) {
        checkInitted();
        Class declaringClass = c.getDeclaringClass();
        //如果聲明這個構造函數的類部是抽象類,則返回一個InstantiationExceptionConstructorAccessorImpl,這其實也是個ConstructorAccessor,只是它對newInstace的實現是直接拋出一個InstantiationException異常
        if (Modifier.isAbstract(declaringClass.getModifiers())) {
            return new InstantiationExceptionConstructorAccessorImpl(null);
        }
        //如果是Class,同上
        if (declaringClass == Class.class) {
            return new InstantiationExceptionConstructorAccessorImpl
                    ("Can not instantiate java.lang.Class");
        }
        //如果是ConstructorAccessorImpl子類,這里會造成無限循環,所以直接通過native方式實例化這個類
        if (Reflection.isSubclassOf(declaringClass,
                ConstructorAccessorImpl.class)) {
            return new BootstrapConstructorAccessorImpl(c);
        }
        //判斷是否啟用了Inflation機制,默認是啟用了。
        //如果沒有啟用Inflation機制,那么通過asm操作字節碼的方式來生成一個ConstructorAccessorImpl類。
        //如果啟用了,那么在執行inflationThreshold(默認15次)次數之前,是通過navite調用來執行newInstance,超過這個次數之后,才會通過asm來生成類。
        //Inflation機制主要是在執行時間和啟動時間上做一個平衡,native方式執行慢但是第一次執行不耗費任何時間,asm生成代碼的方式執行快(20倍),但是第一次生成需要耗費大量的時間。
        //可以通過sun.reflect.noInflation和sun.reflect.inflationThreshold配置型來進行動態配置
        if (noInflation) {
            return new MethodAccessorGenerator().
                    generateConstructor(c.getDeclaringClass(),
                            c.getParameterTypes(),
                            c.getExceptionTypes(),
                            c.getModifiers());
        } else {
            NativeConstructorAccessorImpl acc =
                    new NativeConstructorAccessorImpl(c);
            DelegatingConstructorAccessorImpl res =
                    new DelegatingConstructorAccessorImpl(acc);
            acc.setParent(res);
            return res;
        }
    }

由上面代碼可見,在生成ConstructorAccessorImpl的操作上,一共提供多種版本的實現:直接拋出異常的實現,native執行的實現,asm生成代碼的實現。native的實現比較簡單,asm 版本的主要就是字節碼操作了,比較復雜,暫時不做深入了。


免責聲明!

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



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