新聞中心
1. Class 類的原理

孟子曰:得人心者得天下。而在 Java 中,這個「人心」就是 Class 類,獲取到 Class 類我們就可以為所欲為之為所欲為。下面讓我們深入「人心」,去探索 Class 類的原理。
首先了解 JVM 如何構建實例。
1.1 JVM 構建實例
JVM:Java Virtual Machine,Java 虛擬機。在 JVM 中分為棧、堆、方法區(qū)等,但這些都是 JVM 內(nèi)存,文中所描述的內(nèi)存指的就是 JVM 內(nèi)存。.class 文件是字節(jié)碼文件,是通過 .java 文件編譯得來的。
知道上面這些內(nèi)容,我們開始創(chuàng)建實例。我們以創(chuàng)建 Person 對象舉例:
- Person p = new Person()
簡簡單單通過 new 就創(chuàng)建了對象,那流程是什么樣的呢?見下圖:
這也太粗糙了一些,那在精致一下吧。
同志們發(fā)現(xiàn)沒有,其實這里還是有些區(qū)別的,我告訴你區(qū)別是下面的字比上面多,你會打我不(別打我臉)。
粗糙的那個是通過 new 創(chuàng)建的對象,而精致的是通過 ClassLoader 操作 .class 文件生成 Class 類,然后創(chuàng)建的對象。
其實通過 new 或者反射創(chuàng)建實例,都需要 Class 對象。
1.2 .class 文件
.class 文件在文章開頭講過,是字節(jié)碼文件。.java 是源程序。Java 程序是跨平臺的,一次編譯到處執(zhí)行,而編譯就是從源文件轉換成字節(jié)碼文件。
字節(jié)碼無非就是由 0 和 1 構成的文件。
有如下一個類:
通過 vim 查看一下字節(jié)碼文件:
這啥玩意,看不懂。咱也不需要看懂,反正 JVM 對 .class 文件有它自己的讀取規(guī)則。
1.3 類加載器
還記得上面的精致圖中,我們知道是通過類加載器把 .class 文件加載到內(nèi)存中。具體的類加載器內(nèi)容,我會另寫一篇文章講解(寫完鏈接會更新到這里)。但是核心方法就是 loadClass(),只需要告訴它要加載的 name,它就會幫你加載:
- protected Class> loadClass(String name, boolean resolve)
- throws ClassNotFoundException
- {
- synchronized (getClassLoadingLock(name)) {
- // 1.檢查類是否已經(jīng)加載
- Class> c = findLoadedClass(name);
- if (c == null) {
- long t0 = System.nanoTime();
- try {
- // 2.尚未加載,遵循父優(yōu)先的等級加載機制(雙親委派機制)
- if (parent != null) {
- c = parent.loadClass(name, false);
- } else {
- c = findBootstrapClassOrNull(name);
- }
- } catch (ClassNotFoundException e) {
- // ClassNotFoundException thrown if class not found
- // from the non-null parent class loader
- }
- if (c == null) {
- // 3.如果還沒有加載成功,調(diào)用 findClass()
- long t1 = System.nanoTime();
- c = findClass(name);
- // this is the defining class loader; record the stats
- sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
- sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
- sun.misc.PerfCounter.getFindClasses().increment();
- }
- }
- if (resolve) {
- resolveClass(c);
- }
- return c;
- }
- }
- // 需要重寫該方法,默認就是拋出異常
- protected Class> findClass(String name) throws ClassNotFoundException {
- throw new ClassNotFoundException(name);
- }
- 類加載器加載 .class 文件主要分位三個步驟
- 檢查類是否已經(jīng)加載,如果有就直接返回
- 當前不存在該類,遵循雙親委派機制,加載 .class 文件
上面兩步都失敗,調(diào)用 findClass()
因為 ClassLoader 的 findClass 方法默認拋出異常,需要我們寫一個子類重新覆蓋它,比如:
- @Override
- protected Class> findClass(String name) throws ClassNotFoundException {
- try {
- // 通過IO流從指定位置讀取xxx.class文件得到字節(jié)數(shù)組
- byte[] datas = getClassData(name);
- if (null == datas){
- throw new ClassNotFoundException("類沒有找到:" + name);
- }
- // 調(diào)用類加載器本身的defineClass()方法,由字節(jié)碼得到 class 對象
- return defineClass(name, datas, 0, datas.length);
- }catch (IOException e){
- throw new ClassNotFoundException("類沒有找到:" + name);
- }
- }
- private byte[] getClassData(String name) {
- return byte[] datas;
- }
defineClass 是通過字節(jié)碼獲取 Class 的方法,是 ClassLoader 定義的。我們具體不知道如何實現(xiàn)的,因為最終會調(diào)用一個 native 方法:
- private native Class> defineClass0(String name, byte[] b, int off, int len,
- ProtectionDomain pd);
- private native Class> defineClass1(String name, byte[] b, int off, int len,
- ProtectionDomain pd, String source);
- private native Class> defineClass2(String name, java.nio.ByteBuffer b,
- int off, int len, ProtectionDomain pd,
- String source);
總結下類加載器加載 .class 文件的步驟:
- 通過 ClassLoader 類中 loadClass() 方法獲取 Class
- 從緩存中查找,直接返回
- 緩存中不存在,通過雙親委派機制加載
- 上面兩步都失敗,調(diào)用 findClass()通過 IO 流從指定位置獲取到 .class 文件得到字節(jié)數(shù)組調(diào)用類加載器 defineClass() 方法,由字節(jié)數(shù)組得到 Class 對象
1.4 Class 類
.class 文件已經(jīng)被類加載器加載到內(nèi)存中并生成字節(jié)數(shù)組,JVM 根據(jù)字節(jié)數(shù)組創(chuàng)建了對應的 Class 對象。
接下來我們來分析下 Class 對象。
我們知道 Java 的對象會有下面的信息:
- 權限修飾符
- 類名和泛型信息
- 接口
- 實體
- 注解
- 構造函數(shù)
- 方法
這些信息在 .class 文件以 0101 表示,最后 JVM 會把 .class 文件的信息通過它的方式保存到 Class 中。
在 Class 中肯定有保存這些信息的字段,我們來看一下:
Class 類中用 ReflectionData 里面的字段來與 .class 的內(nèi)容映射,分別映射了字段、方法、構造器和接口。
通過 annotaionData 映射了注解數(shù)據(jù),其它的就不展示了,大家可以自行打開 IDEA 查看下 Class 的源碼。
那我們看看 Class 類的方法
1.4.1 構造器
Class 類的構造器是私有的,只能通過 JVM 創(chuàng)建 Class 對象。所以就有了上面通過類加載器獲取 Class 對象的過程。
1.4.2 Class.forName
Class.forName() 方法還是通過類加載器獲取 Class 對象。
1.4.3 newInstance
newInstance() 的底層是返回無參構造函數(shù)。
2. 總結
我們來梳理下前面的知識點:
反射的關鍵點就是獲取 Class 類,那系統(tǒng)是如何獲取到 Class 類?
是通過類加載器 ClassLoader 將 .class 文件通過字節(jié)數(shù)組的方式加載到 JVM 中,JVM 將字節(jié)數(shù)組轉換成 Class 對象。那類加載器是如何加載的呢?
- 通過 ClassLoader 的 loadClass() 方法
- 從緩存中查找,直接返回
- 緩存中不存在,通過雙親委派機制加載
- 上面兩步都失敗,調(diào)用 findClass()通過 IO 流從指定位置獲取到 .class 文件得到字節(jié)數(shù)組調(diào)用類加載器 defineClass() 方法,由字節(jié)數(shù)組得到 Class 對象
Class 類的構造器是私有的,所以需要通過 JVM 獲取 Class。
Class.forName() 也是通過類加載器獲取的 Class 對象。newInstance 方法的底層也是返回的無參構造函數(shù)。
文章名稱:反射必殺技:深入了解Class類,讓你一通百通
當前URL:http://m.fisionsoft.com.cn/article/djoigeg.html


咨詢
建站咨詢
