從Class原始碼看反射

Liusy01發表於2020-09-21

日常敲碼中,如果想要在程式執行階段訪問某個類的所有資訊,並支援修改類的狀態或者行為的話,肯定會用到反射,而反射靠的就是Class類。Java的動態代理也用到了這個東西,所以瞭解其基本操作在苦逼的CRUD中會新增一絲絲樂趣(有點意思)。

首先來看看Class的操作有哪些?

public final class Class<T> {}

上述程式碼可知,Class是一個由final修飾的泛型類,所以並不能直接通過new Class()獲取其例項。那麼應該如何獲取呢?

//直接通過類的靜態變數來獲取
Class<Integer> intClass = Integer.class;

//通過例項變數的getClass方法
Integer integer = new Integer(0);
Class<? extends Integer> aClass = integer.getClass();

//通過Class.forName("類的全限定名")
Class<?> aClass1 = Class.forName("java.lang.Integer");

上述三種就是獲取某個Class的class例項的方式,需要注意的是,JVM只會載入一個Class例項,也就是說上述三種方式獲取到的class例項都是一樣的。

從Class原始碼看反射

 

而在運用反射的時候,Class.forName是最常用的一種方式。而Class.forName底層會指向forName0這個本地方法

(1)name:類的全限定名

(2)initialize:是否初始化這個類

(3)loader:類載入器

(4)caller:呼叫Class.forName所在類的Class,比如A類程式碼塊裡有Class.forName,那麼caller就是A的class例項。

從Class原始碼看反射

 

通過Class類可以獲取類的例項,構造方法,欄位,成員方法,介面等資訊。獲取之後可以通過API進行相應的操作。

接下來看一下獲取到class例項之後怎麼獲取當前類的例項以及構造方法。

從Class原始碼看反射

 

上述兩種方式都是呼叫預設的無參構造進行例項化物件,那麼怎麼通過公共或私有的有參構造獲取例項呢?

//屬性
int a;
String b ;
//公共有參構造
public ClassSource(int a) {
    this.a = a;
}
//私有有參構造
private ClassSource(String b) {
    this.b = b;
}
public static void main(String[] args){
    Class<?> aClass = Class.forName("com.liusy.lang.ClassSource");
    //獲取public有參構造
    Constructor<?> constructor = aClass.getConstructor(int.class);
    ClassSource o1 = (ClassSource) constructor.newInstance(2);
    System.out.println("屬性【a】的值為:"+ o1.a);
    //獲取private有參構造
    Constructor<?> constructor1 = aClass.getDeclaredConstructor(String.class);
    //這個有貓膩
    constructor1.setAccessible(true);
    ClassSource o2 = (ClassSource) constructor1.newInstance("abc");
    System.out.println("屬性【b】的值為:"+ o2.b);
}

上述程式碼執行的結果如下:

從Class原始碼看反射

 

而獲取私有建構函式最重要的是要setAccessible(true)這個方法,點進去看一下,這個方法最後是給override欄位賦值的,可用於類的欄位、方法以及建構函式。


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){
      if (obj instanceof Constructor && flag == true) {
          Constructor<?> c = (Constructor<?>)obj;
          if (c.getDeclaringClass() == Class.class) {
              //如果是Class.class,則拋異常
          }
      }
      //最終是設定此屬性
      obj.override = flag;
}    

//訪問級別是否可以被覆蓋,可用於欄位,方法,建構函式 
boolean override;

需要注意的是,在程式碼中用反射操作當前類私有欄位、私有方法或其他私有屬性時,並不需要setAccessible(true),例如在A類中用反射操作A類的私有屬性。只有在A類中操作其他類的私有屬性才需要設定setAccessible(true)。

不管是Class.newInstance還是Constructor.newInstance,底層呼叫的都是ConstructorAccessor(建構函式訪問器)介面的newInstance方法。

(1)Class.newInstance

//標識所有公共屬性
public static final int PUBLIC = 0;
//標識所有私有屬性
public static final int DECLARED = 1;

public T newInstance(){
    //省略部分程式碼 
    // 檢查有沒有存在已載入過的構造器
    if (cachedConstructor == null) {
        //省略部分程式碼 
        try {
            Class<?>[] empty = {};
                //獲取預設私有無參構造
            final Constructor<T> c = getConstructor0(empty, Member.DECLARED);
                //省略部分程式碼 
            cachedConstructor = c;
        } catch (NoSuchMethodException e) {
            throw (InstantiationException)
                new InstantiationException(getName()).initCause(e);
        }
    }
    Constructor<T> tmpConstructor = cachedConstructor;
    //省略部分程式碼 
    try {
            //呼叫無參構造的newInstance方法
        return tmpConstructor.newInstance((Object[])null);
    } catch (InvocationTargetException e) {
        //省略部分程式碼 
        return null;
    }
}
 
 private Constructor<T> getConstructor0(Class<?>[] parameterTypes,
                                        int which) 
{
    //獲取建構函式,重點看這個方法,引數是false
    Constructor<T>[] constructors = privateGetDeclaredConstructors((which == Member.PUBLIC));
    //省略匹配建構函式引數的程式碼    
}
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;
    }
    // 檢查是否是介面 
    if (isInterface()) {
        //程式碼省略
    } else {
       //底層呼叫的是本地方法 
        res = getDeclaredConstructors0(publicOnly);
    }
    if (rd != null) {
        if (publicOnly) {
            rd.publicConstructors = res;
        } else {
            rd.declaredConstructors = res;
        }
    }
    return res;
}

如上,可以看到Class.newInstance底層是呼叫無參構造的newInstance方法。

(2)Constructor.newInstance

public T newInstance(Object ... initargs){
        //override就是是否可覆蓋級別訪問
        if (!override) {
            if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
                Class<?> caller = Reflection.getCallerClass();
                checkAccess(caller, clazz, null, modifiers);
            }
        }
        ConstructorAccessor ca = constructorAccessor;   // read volatile
        if (ca == null) {
            ca = acquireConstructorAccessor();
        }
        @SuppressWarnings("unchecked")
        T inst = (T) ca.newInstance(initargs);
        return inst;
    }

public interface ConstructorAccessor {
    Object newInstance(Object[] var1) throws InstantiationException, IllegalArgumentException, InvocationTargetException;
}

可以看到,Class.newInstance呼叫的是無參構造的newInstance,而建構函式呼叫的是ConstructorAccessor介面類的newInstance,所以最終呼叫的都是ConstructorAccessor介面類的newInstance。

最終呼叫的是NativeConstructorAccessorImpl的newInstance方法。

從Class原始碼看反射

 

【注】以下僅演示操作方法,原始碼不貼,防止篇幅太長。

獲取例項以及構造方法之後,來看一下如何訪問,修改類欄位資訊。

  //public屬性
public int a;
//private屬性
private String b;
public static void main(String[] args) throws Exception {

    Class<?> aClass = Class.forName("com.liusy.lang.ClassSource");
    //獲取例項
    Object instance = aClass.newInstance();
    //根據欄位名獲取公共屬性並進行賦值
    Field a = aClass.getField("a");
    a.set(instance,2);
    System.out.println("a的值為:"+a.get(instance));
    //根據欄位名獲取私有屬性並進行賦值
    Field b = aClass.getDeclaredField("b");
    b.setAccessible(true);
    b.set(instance,"abc");
    System.out.println("b的值為"+b.get(instance));
}

上述程式碼執行結果為:

從Class原始碼看反射

 

接下來瞧一下如何利用class訪問類的成員方法:

public static void main(String[] args) throws Exception {

    Class<?> aClass = Class.forName("com.liusy.lang.ClassSource");
    //獲取例項
    Object instance = aClass.newInstance();

    //獲取公共方法並呼叫
    Method sayPublicHello = aClass.getMethod("sayPublicHello");
    sayPublicHello.invoke(instance);

    //獲取私有方法並呼叫
    Method sayPrivateHello = aClass.getDeclaredMethod("sayPrivateHello", int.class);
    sayPrivateHello.setAccessible(true);
    sayPrivateHello.invoke(instance,1);
}

public void sayPublicHello(){
    System.out.println("sayPublicHello");
}

private void sayPrivateHello(int param){
    System.out.println("sayPrivateHello,引數:"+param);
}

上述程式碼執行結果為:

從Class原始碼看反射

 

還可以使用“==”判斷資料型別

Class<?> aClass = Class.forName("com.liusy.lang.ClassSource");
System.out.println(aClass == ClassSource.class);

之前遇到過用了很多反射的程式碼,看起來很噁心。但其實瞭解過後反而覺得反射是個很強大的東西,包括JDK動態代理,spring AOP,事務底層都是用的反射,研究之後有利於瞭解更多底層的知識。

=======================================================

我是Liusy,一個喜歡健身的程式設計師。

歡迎關注微信公眾號【Liusy01】,一起交流Java技術及健身,獲取更多幹貨。

相關文章