1. 程式人生 > >Java原始碼分析——java.lang.reflect反射包解析(一) AccessibleObject、ReflectionFactory、Filed、Method、Constructor類

Java原始碼分析——java.lang.reflect反射包解析(一) AccessibleObject、ReflectionFactory、Filed、Method、Constructor類

    Java的反射機制一直是被人稱讚的,它的定義是:程式在執行中時,對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意一個物件,都能夠呼叫它的任意一個方法和屬性。簡單的來說就是可以通過Java的反射機制知道自己想知道的類的一切資訊。

    在Java的反射機制中,類中的三個組成部分便是其重點,也就是FiledMethodConstructor類。它們分別負責類的屬性、類的方法與類的構造方法。關於它們之間的關係,如下圖所示,方便讀者的理解:
在這裡插入圖片描述

    從UML途中可以看出,Filed、Method、Constructor類都繼承著同一個父類,AccessibleObject類,而Method與Constructor類的直接父類Executable類實現了GenericDeclaration介面,表明Method與Constructor類可以實現泛型,即型別變數,切記Filed類沒有直接實現型別變數介面,但是可以使用泛型,泛型不等於型別變數。下面來分別講解它們的實用以及原始碼。

AccessibleObject類

    AccessibleObject類作為三者的共同父類,起著檢查訪問許可權,實現註解相關方法的作用,其中重點是許可權的更改與判定,在AccessibleObject類開頭便用靜態常量定義了一個許可權屬性:

 static final private java.security.Permission ACCESS_PERMISSION =
        new ReflectPermission("suppressAccessChecks");

    這個許可權表明訪問者可以能夠訪問類中的欄位和呼叫方法。注意,這不僅包括public,而且還包括 protected 和 private 欄位和方法。而該類提供了幾個關鍵的方法來用這個屬性修改對應方法及屬性的許可權,下面列出常見的一種:

 public void setAccessible(boolean flag) throws SecurityException {
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) sm.checkPermission(ACCESS_PERMISSION);
        setAccessible0(this, flag);
    }
    
    private static void setAccessible0(AccessibleObject obj, boolean
flag) throws SecurityException { if (obj instanceof Constructor && flag == true) { Constructor<?> c = (Constructor<?>)obj; if (c.getDeclaringClass() == Class.class) { throw new SecurityException("Cannot make a java.lang.Class" + " constructor accessible"); } } obj.override = flag; }

    從程式碼中可以看出,setAccessible方法獲取了java的安全管理器,通過安全管理器來更改AccessibleObject物件的許可權,但是setAccessible0方法中看出,AccessibleObject物件是構造方法且該類的Class類物件就是Class類本身時,會丟擲異常,也就是不能更改許可權。而override屬性為true時,表明這個成員不需要進行訪問許可權檢查。展示更改許可權的示例程式碼:

class KT{
    private KT(){
        System.out.println("啦啦啦啦");
    }
}
public  class Test {
    public static void main(String args[]) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        Class test=Class.forName("test.KT");
        Constructor []constructor=test.getDeclaredConstructors();
        //constructor[0].setAccessible(true);
        System.out.println(constructor[0].newInstance());
    }
}

    執行結果為:java.lang.IllegalAccessException: Class test.Test can not access a member of class 。 也就是沒有該成員的訪問許可權,將註釋去掉再次執行,結果為打印出:啦啦啦啦 [email protected] 可見確實更改了訪問許可權。許可權更改用起來挺舒服的,但是這個許可權的更改帶來一個直接的弊端就是會導致類中保密資訊的洩露以及破壞類的封裝性,所以不到迫不得已不要使用更改許可權這一個方法。

ReflectionFactory類

    為什麼要說這個類呢?如其名所示,是一個反射的工廠類,既然是工廠類那麼它就擔任著類的例項化的操作,但是這個工廠類只是深度克隆下面要講的三個類:Filed類、Method類、Constructor類。並不直接建立,只是複製一份。其中有著三個很重要的方法:

public FieldAccessor newFieldAccessor(Field var1, boolean var2)public MethodAccessor newMethodAccessor(Method var1)public ConstructorAccessor newConstructorAccessor(Constructor<?> var1)

    這三個方法分別對應著Filed類、Method類、Constructor類的訪問許可權,也就是說,這三個方法返回的物件都是用來判定三者的修飾符許可權的,也就是實現了當方法是私有方法時,外部不能訪問它的功能的,原理是當外部要訪問這個私有方法時,會建立一個方法的MethodAccessor,並在裡面進行相關的判斷,判斷如果是訪問的許可權那麼允許訪問者訪問,否則拒絕丟擲異常,其它兩者也是一樣的,而且不僅僅會判斷修飾符的許可權也會判斷其屬性的型別,方法的型別等。

Filed類

    Filed類是類的屬性類,直接繼承自AccessibleObject類,還實現了Member介面,它負責管理一切類的屬性。怎麼獲取Filed例項呢?是通過Class類物件的getDeclaredField方法,該方法的兩種過載格式定義如下:

public Field[] getDeclaredFields() throws SecurityException {
        checkMemberAccess(Member.DECLARED, Reflection.getCallerClass(), true);
        return copyFields(privateGetDeclaredFields(false));
    }
    
public Field getDeclaredField(String name)
        throws NoSuchFieldException, SecurityException {
        checkMemberAccess(Member.DECLARED, Reflection.getCallerClass(), true);
        Field field = searchFields(privateGetDeclaredFields(false), name);
        if (field == null) {
            throw new NoSuchFieldException(name);
        }
        return field;
    }

    第一種過載格式是獲取類中所有的屬性,而第二種則是獲取指定屬性名的Filed物件。兩種過載方法裡都先檢查一遍是否有許可權查詢,再呼叫privateGetDeclaredFields方法取找對應的Filed。以第二種過載格式來講解如何獲得一個Filed物件,分析程式碼可知,getDeclaredField方法呼叫了searchFields方法,來看searchFields方法的原始碼:

 private static Field searchFields(Field[] fields, String name) {
        String internedName = name.intern();
        for (int i = 0; i < fields.length; i++) {
            if (fields[i].getName() == internedName) {
                return getReflectionFactory().copyField(fields[i]);
            }
        }
        return null;
    }

    searchFields方法是在已知Filed陣列的情況下,找到對應匹配的Filed物件,並呼叫ReflectionFactory類的複製方法深度克隆一個Filed物件。而已知Filed陣列的獲取是有privateGetDeclaredFields方法來執行的,它的定義如下:

private Field[] privateGetDeclaredFields(boolean publicOnly) {
       ......    
        res = Reflection.filterFields(this, getDeclaredFields0(publicOnly));    
       ......      
    }
    
private native Field[]       getDeclaredFields0(boolean publicOnly);

    在privateGetDeclaredFields方法裡你會發現其實呼叫了native方法來查詢所有的關於對應類的屬性,publicOnlytrue時,只查詢public的屬性,否則查詢所有的。Method類與Constructor類的獲取與Filed類的獲取原理一樣,故下文不再重複,要注意的是,它們三者的獲取都是深度克隆來的並不是直接獲取。

    Filed類的重要的方法如下,也就是獲取對應屬性的值:

public Object get(Object obj)
        throws IllegalArgumentException, IllegalAccessException
    {
        if (!override) {
            if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
                Class<?> caller = Reflection.getCallerClass();
                checkAccess(caller, clazz, obj, modifiers);
            }
        }
        return getFieldAccessor(obj).get(obj);
    }

    get方法是獲取一個例項話的物件其中的屬性的值的,在方法裡先判斷許可權,後呼叫FieldAccessor屬性訪問者來獲判斷是否許可權合格,然後通過Filed物件判斷其型別,再呼叫相關的函式返回傳進來的屬性的值,上述是getFieldAccessor方法的實現過程。物件示例程式碼如下:

class KT{
    private   int filed=9;
}
public  class Test {
    public static void main(String args[]) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
        Class test=Class.forName("test.KT");
        KT kt=(KT)test.newInstance();
        Field field=test.getDeclaredField("filed");
        field.setAccessible(true);
        System.out.println("原來的值");
        System.out.println(field.get(kt));
        field.set(kt,10);
        System.out.println("更改後的值");
        System.out.println(field.get(kt));
    }
}

在這裡插入圖片描述

Executable類

    Executable類是一個抽象類,直接繼承AccessibleObject類,並實現Membet與GenericDeclaration介面,主要作用是實現型別變數的相關操作以及展示方法的資訊。主要的方法如下:

String sharedToString(int modifierMask,
                          boolean isDefault,
                          Class<?>[] parameterTypes,
                          Class<?>[] exceptionTypes);
                        
String sharedToGenericString(int modifierMask, boolean isDefault);

    這兩個方法都會被Method類以及Constructor類展示自身的所有資訊以及型別變數的資訊的。

Method類

    Method類繼承自Executable抽象類,負責管理所有類的方法,其主要的方法如下:

 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();
        }
        //呼叫MethodAccessor的invoke方法
        return ma.invoke(obj, args);
    }

    與Filed類一樣,先判斷是否取消了許可權的檢查,再通過MethodAccessor介面來判斷許可權,後呼叫其invoke方法。當你點開MethodAccessor時,你會發現它是個介面:

public interface MethodAccessor {
    Object invoke(Object var1, Object[] var2) throws IllegalArgumentException, InvocationTargetException;
}

    而MethodAccessor介面的實現需要到NativeMethodAccessorImpl類中去檢視,其中實現的invoke方法底層呼叫的就是native本地方法實現的:

private static native Object invoke0(Method var0, Object var1, Object[] var2);

    這個方法主要作用是用來反向呼叫方法的,也就是通過反射的方法來呼叫對應類中的方法,示例程式碼如下:

class KT{
    public String print(String name){
        return name;
    }
}
public  class Test {
    public static void main(String args[]) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
        Class test=Class.forName("test.KT");
        KT kt=(KT)test.newInstance();
        Method method=test.getDeclaredMethod("print", String.class);
        System.out.println(method.invoke(kt,"lalalal"));
    }
}
//輸出為:lalalal

Constructor類

    Constructor類直接繼承自Executable類,負責管理所有的構造方法,其主要方法如下:

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物件
        ConstructorAccessor ca = constructorAccessor;   // read volatile
        if (ca == null) {
			//從反射工廠裡獲取ConstructorAccessor物件
            ca = acquireConstructorAccessor();
        }
        @SuppressWarnings("unchecked")
        T inst = (T) ca.newInstance(initargs);
        return inst;
    }

    當你檢視ConstructorAccessor時,也會發現是個介面,其具體實現是NativeConstructorAccessorImpl類,其中實現的newInstance方法就呼叫了native方法來建立例項物件:

private static native Object newInstance0(Constructor<?> var0, Object[] var1) throws InstantiationException, IllegalArgumentException, InvocationTargetException;

其示例程式碼如下:

class KT{
    public  String name;
    public KT(String name){
        this.name=name;

    }
}
public  class Test {
    public static void main(String args[]) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
        Class test=Class.forName("test.KT");
        Constructor constructor=test.getDeclaredConstructor(String.class);
        KT kt=(KT)constructor.newInstance("hahahh");
        System.out.println(kt.name);
    }
}
//打印出:hahahh

    從結果可以看出,類被成功的用Constructor的newInstance方法給例項化了。

    雖然反射很強大,呼叫很靈活,但是不要輕易的去使用反射去建立例項,呼叫方法,因為反射雖然優點很多,但是缺點也很明顯,它最大的缺點就是耗時多,它比平常的普通例項化耗費的時間多,因為平常的呼叫是已經例項好的,直接呼叫即可,而反射的的呼叫則是需要建立複製體,然後通過jvm查詢對應的屬性、方法或者構造方法的。