jdk源碼分析(二)——Class類

一.概述

我們在<a href="http://www.reibang.com/p/4791207253a0">jdk源碼分析(一)</a>中講了Object類属划,今天要講的Class類同樣非吃矗基礎(chǔ)。java程序中所有正在運行中的類和接口都是Class類的實例。枚舉類型骚亿,數(shù)據(jù),所有的java基礎(chǔ)類型以及關(guān)鍵字void都是Class實例悦污。
類聲明:

public final class Class<T> implements java.io.Serializable,
        java.lang.reflect.GenericDeclaration,
        java.lang.reflect.Type,
        java.lang.reflect.AnnotatedElement

Class類中方法非常多代芜,有超過50個方法,但是經(jīng)過梳理瘸味,我們可以發(fā)現(xiàn)宫仗,其實方法主要有三類:生成實例、獲取類信息旁仿、類型轉(zhuǎn)換藕夫。

下面我們分別來看一下這三類方法。

二.生成實例

此類方法的主要目的是根據(jù)給定的類名枯冈,加載類信息并執(zhí)行初始化毅贮。然后可以根據(jù)已經(jīng)完成初始化的信息生成類的實例。
這一類方法主要有兩個:
forName方法和newInstance方法尘奏。
我們先來看forName方法滩褥,這個方法相信大家都不陌生,如果你曾經(jīng)使用過jdbc進行mysql數(shù)據(jù)庫操作炫加,那么你一定寫過如下代碼:

String driver = "com.mysql.jdbc.Driver";
String url = "jdbc:mysql://localhost:3306/test";
String username = "root";
String password = "123456";
Connection conn = null;
try {
    Class.forName(driver);
    conn = DriverManager.getConnection(url, username, password);
} catch (ClassNotFoundException e) {
    e.printStackTrace();
} catch (SQLException e) {
    e.printStackTrace();
}

我們這里只看forName方法瑰煎。forName方法有兩種聲明:

public static Class<?> forName(String className)
            throws ClassNotFoundException {
    return forName0(className, true, ClassLoader.getCallerClassLoader());
}

public static Class<?> forName(String name, boolean initialize,
                                   ClassLoader loader)
            throws ClassNotFoundException {
    // 如果傳入的類加載器為null,則獲取調(diào)用類的類加載器
    if (loader == null) {
        // 獲取安全管理器
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            // 獲取調(diào)用類的類加載器
            ClassLoader ccl = ClassLoader.getCallerClassLoader();
            if (ccl != null) {
                // 使用安全管理器檢查是否有獲取類加載器的權(quán)限
                sm.checkPermission(
                    SecurityConstants.GET_CLASSLOADER_PERMISSION);
            }
        }
    }
    return forName0(name, initialize, loader)
}

從以上代碼可以看到俗孝,第一個forName方法相當于調(diào)用

forName(name, true, null)

最終兩個forName方法都調(diào)用了forName0方法:

private static native Class forName0(String name, boolean initialize,
                                                          ClassLoader loader)
            throws ClassNotFoundException;

這是一個本地方法丢间,我們暫且不深究,只需知道forName0方法將根據(jù)指定類完全限定名驹针,以及類加載器完成類的加載烘挫,如果需要,還將執(zhí)行類的初始化操作。

在forName方法實現(xiàn)中饮六,出現(xiàn)了兩個類其垄,一個是SecurityManager,另一個是ClassLoader卤橄,其中SecurityManager是jvm提供的在應(yīng)用層進行安全檢查的機制绿满,應(yīng)用程序可以根據(jù)策略文件被賦予一定的權(quán)限,例如是否可以讀寫文件窟扑,是否可以讀寫網(wǎng)絡(luò)端口喇颁,是否可以讀寫內(nèi)存,是否可以獲取類加載器……嚎货。在進行特殊操作時橘霎,需要進行安全檢查,從而給程序的運行安全提供一定保障殖属。

而ClassLoader則涉及到另一個大的話題:類加載姐叁,此處我們暫時先不深入展開,只需知道我們程序運行中使用的類都需要由類加載器來完成加載洗显,并執(zhí)行一定的初始化外潜,隨后才可以被我們使用。

類完成加載后挠唆,通常需要被實例化处窥,而這就會用到newInstance方法了。

// 獲取類的實例
public T newInstance()
        throws InstantiationException, IllegalAccessException {
    // 進行安全檢查
    if (System.getSecurityManager() != null) {
        checkMemberAccess(Member.PUBLIC, ClassLoader.getCallerClassLoader(), false);
    }
    return newInstance0();
}

/**
 * 檢查客戶端是否有成員的訪問權(quán)限
 * @param which 權(quán)限識別碼玄组,整數(shù)類型
 * @param ccl 類加載器
 * @param checkProxyInterfaces 
 */
private void checkMemberAccess(int which, ClassLoader ccl, boolean checkProxyInterfaces) {
    SecurityManager s = System.getSecurityManager();
    if (s != null) {
        // 檢查客戶端能否有權(quán)限訪問該類
        s.checkMemberAccess(this, which);
        ClassLoader cl = getClassLoader0();
        if (ReflectUtil.needsPackageAccessCheck(ccl, cl)) {
            // 獲取類的完全限定名
            String name = this.getName();
            int i = name.lastIndexOf('.');
            // 類的完全限定名中含有.(例如com.Test)
            if (i != -1) {
                // 獲取包名
                String pkg = name.substring(0, i);
                // 如果該類是com.sun.proxy包下的代理類碧库,則不需要進行包訪問權(quán)限驗證,否則需要驗證
                // 代理類指使用Proxy.getProxyClass或Proxy.newProxyInstance動態(tài)生成的類
                if (!Proxy.isProxyClass(this) || !pkg.equals(ReflectUtil.PROXY_PACKAGE)) {
                    s.checkPackageAccess(pkg);
                }
            }
        }
        // 如果該類是代理類巧勤,并且需要檢查代理類的接口嵌灰,則檢查接口的訪問權(quán)限
        if (checkProxyInterfaces && Proxy.isProxyClass(this)) {
            ReflectUtil.checkProxyPackageAccess(ccl, this.getInterfaces());
        }
    }
}

private T newInstance0() throws InstantiationException, IllegalAccessException {
    // 每次先檢查是否有已經(jīng)緩存過的構(gòu)造器,如果沒有颅悉,則重新獲取
    if (cachedConstructor == null) {
        // 如果該類是Class類沽瞭,則直接拋出異常,意思是剩瓶,我們不能利用此方法生成Class類的實例
        if (this == Class.class) {
            throw new IllegalAccessException(
                    "Can not call newInstance() on the Class for java.lang.Class"
            );
        }
        try {
            Class[] empty = {};
            // 獲取該類已經(jīng)聲明的的無參構(gòu)造方法
            final Constructor<T> c = getConstructor0(empty, Member.DECLARED);
            // 此處是為了使無參構(gòu)造方法可以被訪問驹溃,因為有時,構(gòu)造方法被聲明為private的
            java.security.AccessController.doPrivileged
                    (new java.security.PrivilegedAction() {
                        public Object run() {
                            c.setAccessible(true);
                            return null;
                        }
                    });
            // 將獲取的無參構(gòu)造方法緩存
            cachedConstructor = c;
        } catch (NoSuchMethodException e) {
            throw new InstantiationException(getName());
        }
    }
    Constructor<T> tmpConstructor = cachedConstructor;
    // 獲取構(gòu)造方法的語言修飾符延曙,諸如public,private,static,final等
    int modifiers = tmpConstructor.getModifiers();
    // 根據(jù)已經(jīng)獲取的語言修飾符判斷是否具有訪問權(quán)限豌鹤,如果沒有,則執(zhí)行以下操作
    if (!Reflection.quickCheckMemberAccess(this, modifiers)) {
        // 獲取調(diào)用者的類
        Class caller = Reflection.getCallerClass(3);
        // 與之前緩存的調(diào)用者類進行對比枝缔,如果不是之前的布疙,則需重新確保調(diào)用者類可以訪問無參構(gòu)造方法
        if (newInstanceCallerCache != caller) {
            Reflection.ensureMemberAccess(caller, this, null, modifiers);
            newInstanceCallerCache = caller;
        }
    }
    try {
        // 整個方法的核心蚊惯,使用類的構(gòu)造方法來生成實例
        return tmpConstructor.newInstance((Object[]) null);
    } catch (InvocationTargetException e) {
        Unsafe.getUnsafe().throwException(e.getTargetException());
        return null;
    }
}

以上就是newInstance的具體實現(xiàn),本質(zhì)上是獲取的類的無參構(gòu)造方法灵临,然后執(zhí)行無參構(gòu)造方法來生成實例截型。

三.獲取類信息

這一類方法非常多,作用包括獲取類的構(gòu)造方法儒溉,已經(jīng)聲明的字段宦焦、方法,獲取類或者方法的注解顿涣,獲取類的包名波闹、父類,以及判斷類是否是數(shù)組涛碑、是否是枚舉精堕、是否是接口。由于方法眾多锌唾,此處不一一列舉,只選擇一個看看究竟夺英,不妨看看getDeclaredMethods方法晌涕。

// 返回類中聲明的方法,核心實現(xiàn)在privateGetDeclaredMethods和copyMethods中
public Method[] getDeclaredMethods() throws SecurityException {
    checkMemberAccess(Member.DECLARED, ClassLoader.getCallerClassLoader(), true);
    return copyMethods(privateGetDeclaredMethods(false));
}

// 拷貝方法數(shù)組痛悯,使用ReflectionFactory來拷貝
//個人理解之所以需要拷貝是因為不想讓獲取的方法被隨意修改
private static Method[] copyMethods(Method[] arg) {
    Method[] out = new Method[arg.length];
    ReflectionFactory fact = getReflectionFactory();
    for (int i = 0; i < arg.length; i++) {
        out[i] = fact.copyMethod(arg[i]);
    }
    return out;
}

/**
 * 獲取類中聲明方法的具體實現(xiàn)
 * @param publicOnly 是否只獲取public方法
 * @return 方法數(shù)組
 */
private Method[] privateGetDeclaredMethods(boolean publicOnly) {
    // 等待系統(tǒng)內(nèi)部類初始化完成余黎,系統(tǒng)屬性(sun.reflect.noCaches)被解析完成
    checkInitted();
    Method[] res = null;
    // 根據(jù)用戶指定的系統(tǒng)屬性sun.reflect.noCaches值來決定是否使用緩存,默認使用緩存
    if (useCaches) {
        // 清空緩存载萌,將緩存的declaredFields惧财、declaredMethods、annotations等設(shè)置為null
        clearCachesOnClassRedefinition();
        // 如果只獲取public方法
        if (publicOnly) {
            // 如果declaredPublicFields緩存可用扭仁,則直接從緩存中獲取
            if (declaredPublicMethods != null) {
                res = (Method[]) declaredPublicMethods.get();
            }
        } else {
            // 如果declaredMethods緩存可用垮衷,則直接從緩存中獲取
            if (declaredMethods != null) {
                res = (Method[]) declaredMethods.get();
            }
        }
        if (res != null) return res;
    }
    // 不使用緩存,則需要調(diào)用本地方法進行獲取
    res = getDeclaredMethods0(publicOnly);
    // 如果可以使用緩存乖坠,則設(shè)置緩存搀突,以備下次使用
    if (useCaches) {
        if (publicOnly) {
            declaredPublicMethods = new SoftReference(res);
        } else {
            declaredMethods = new SoftReference(res);
        }
    }
    return res;
}

private native Method[] getDeclaredMethods0(boolean publicOnly);

四.類型轉(zhuǎn)換

該類方法主要有兩個:

/**
 * 將類轉(zhuǎn)換為它的子類Class
 * @param clazz 父類的Class
 * @param <U> 父類
 * @return U的子類Class
 */
public <U> Class<? extends U> asSubclass(Class<U> clazz) {
    // 判斷clazz是否是當前類,或者是當前類的父類
    if (clazz.isAssignableFrom(this))
        return (Class<? extends U>) this;
    else
        throw new ClassCastException(this.toString());
}

/**
 * 將給定的類轉(zhuǎn)換為當前Class所代表的類
 * @param obj 需要轉(zhuǎn)換的類
 * @return 當前Class所代表的類
 */
public T cast(Object obj) {
    // obj不為null熊泵,并且可以被轉(zhuǎn)換為當前Class代表的類
    // isInstance為native方法仰迁,類似于instanceOf的作用
    if (obj != null && !isInstance(obj))
        throw new ClassCastException();
    return (T) obj;
}

五.使用示例

class Car {
    private String name = "car";
    public String getName() {
        return name;
    }
}

class SUV extends Car {
    private String name = "suv";

    @Override
    public String getName() {
        return name;
    }
}

class Tiguan extends SUV {
    private String name = "tiguan";

    @Override
    public String getName() {
        return name;
    }
}

public class ClassTest {

    public static void main(String[] args) {
        try {
            // 使用forName方法與newInstance方法生成實例
            Car tiguanCar = (Car) Class.forName("Tiguan").newInstance();
            // 輸出"tiguan"
            System.out.println(tiguanCar.getName());
            // 從上面的輸出可以看出,tiguanCar是一個Tiguan實例顽分,將其轉(zhuǎn)換為SUV的類
            // 注意徐许,此處只能窄化tiguanCar的范圍,本質(zhì)上仍然是Tiguan.class
            Class<? extends SUV> tiguanClass = tiguanCar.getClass().asSubclass(SUV.class);
            // 輸出"class Tiguan"
            System.out.println(tiguanClass);
            // 將tiguanCar轉(zhuǎn)換為SUV實例,tiguanCar是一個Tiguan類的實例卒蘸,作用與(SUV) tiguanCar一樣雌隅。
            SUV suv = SUV.class.cast(tiguanCar);
            // 輸出"tiguan"
            System.out.println(suv.getName());
            // 獲取Tiguan類聲明的字段
            Field[] tiguanFields = tiguanClass.getDeclaredFields();
            // 輸出"name"
            for (Field field : tiguanFields) {
                System.out.println(field.getName());
            }
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

參考資料

  1. java Class類詳解
  2. Java反射相關(guān)類源碼淺析-Class類
  3. Class.asSubclass淺談
  4. Class.asSubclass signature
  5. Java安全管理器——SecurityManager
  6. java Class.forName詳解

另外本文中結(jié)構(gòu)圖使用了ProcessOn免費在線作圖工具,感覺很贊,推薦一下澄步。

本文已遷移至我的博客:http://ipenge.com/11304.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末冰蘑,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子村缸,更是在濱河造成了極大的恐慌祠肥,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,042評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件梯皿,死亡現(xiàn)場離奇詭異仇箱,居然都是意外死亡,警方通過查閱死者的電腦和手機东羹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評論 2 384
  • 文/潘曉璐 我一進店門剂桥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人属提,你說我怎么就攤上這事权逗。” “怎么了冤议?”我有些...
    開封第一講書人閱讀 156,674評論 0 345
  • 文/不壞的土叔 我叫張陵斟薇,是天一觀的道長。 經(jīng)常有香客問我恕酸,道長堪滨,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,340評論 1 283
  • 正文 為了忘掉前任蕊温,我火速辦了婚禮袱箱,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘义矛。我一直安慰自己发笔,他們只是感情好,可當我...
    茶點故事閱讀 65,404評論 5 384
  • 文/花漫 我一把揭開白布凉翻。 她就那樣靜靜地躺著筐咧,像睡著了一般。 火紅的嫁衣襯著肌膚如雪噪矛。 梳的紋絲不亂的頭發(fā)上量蕊,一...
    開封第一講書人閱讀 49,749評論 1 289
  • 那天,我揣著相機與錄音艇挨,去河邊找鬼残炮。 笑死,一個胖子當著我的面吹牛缩滨,可吹牛的內(nèi)容都是我干的势就。 我是一名探鬼主播泉瞻,決...
    沈念sama閱讀 38,902評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼苞冯!你這毒婦竟也來了袖牙?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,662評論 0 266
  • 序言:老撾萬榮一對情侶失蹤舅锄,失蹤者是張志新(化名)和其女友劉穎鞭达,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體皇忿,經(jīng)...
    沈念sama閱讀 44,110評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡畴蹭,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了鳍烁。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片叨襟。...
    茶點故事閱讀 38,577評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖幔荒,靈堂內(nèi)的尸體忽然破棺而出糊闽,到底是詐尸還是另有隱情,我是刑警寧澤爹梁,帶...
    沈念sama閱讀 34,258評論 4 328
  • 正文 年R本政府宣布右犹,位于F島的核電站,受9級特大地震影響卫键,放射性物質(zhì)發(fā)生泄漏傀履。R本人自食惡果不足惜虱朵,卻給世界環(huán)境...
    茶點故事閱讀 39,848評論 3 312
  • 文/蒙蒙 一莉炉、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧碴犬,春花似錦絮宁、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至偿荷,卻和暖如春窘游,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背跳纳。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評論 1 264
  • 我被黑心中介騙來泰國打工忍饰, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人寺庄。 一個月前我還...
    沈念sama閱讀 46,271評論 2 360
  • 正文 我出身青樓艾蓝,卻偏偏與公主長得像力崇,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子赢织,可洞房花燭夜當晚...
    茶點故事閱讀 43,452評論 2 348

推薦閱讀更多精彩內(nèi)容

  • 作者:成 富, 軟件工程師, IBM 中國軟件開發(fā)中心 類加載器(class loader)是 Java?中的一個...
    Android技術(shù)研究閱讀 3,900評論 0 74
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法亮靴,類相關(guān)的語法,內(nèi)部類的語法于置,繼承相關(guān)的語法茧吊,異常的語法,線程的語...
    子非魚_t_閱讀 31,597評論 18 399
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理俱两,服務(wù)發(fā)現(xiàn)饱狂,斷路器,智...
    卡卡羅2017閱讀 134,628評論 18 139
  • 類加載器是 Java 語言的一個創(chuàng)新宪彩,也是 Java 語言流行的重要原因之一休讳。它使得 Java 類可以被動態(tài)加載到...
    CHSmile閱讀 1,596評論 0 12
  • 生活并不是短跑俊柔,而是一場馬拉松,投資亦是如此活合。 1雏婶、盤面一覽 周二大盤開盤后,驚魂未定的市場依舊弱勢下探白指,...
    阿凱古閱讀 343評論 2 3