1. 程式人生 > 其它 >Java--ClassLoader 類載入機制與重寫類載入

Java--ClassLoader 類載入機制與重寫類載入

1.ClassLoader

Java是依賴JVM實現的跨平臺開發,程式執行前需要先編譯class檔案,

Java類初始化的時候會呼叫java.lang.Classloader來載入位元組碼,

然後ClasssLoader呼叫JVM的native方法來定義一個java.lang.Class例項。

2.Java類

public class TestHello {
    public String hello(){
        return "hello,world!";
    }
}

這裡編譯成一個java檔案

使用javap -c 命令反彙編class檔案

JVM再執行我們的TestHello時候會先解析class的二進位制內容,其實就是javap命令生成的位元組碼。

3.類載入方法

所有的java類都必須經過JVM載入後才能執行,ClassLoader主要作用就是用於Java類檔案的載入。

在JVM類載入器中最頂層的是Bootstrap ClassLoader(引導類載入器)Extension ClassLoader(擴充套件類載入器)(接觸較少App ClassLoader(系統類載入器)(直接載入我們的程式碼AppClassLoader是預設的類載入器,如果類載入時我們不指定類載入器的情況下,預設會使用AppClassLoader載入類。

可以這麼說 用java.io.File.class.getClassLoader()取得是null的物件,就是用Bootstrap

去載入的,以為它是用C++去實現的,所以當然得不到對應的物件了!

ClassLoader類有如下核心方法:

  1. loadClass(載入指定的Java類)
  2. findClass(查詢指定的Java類)
  3. findLoadedClass(查詢JVM已經載入過的類)
  4. defineClass(定義一個Java類)
  5. resolveClass(連結指定的Java類)

4.Java動態載入方式

Java的載入方式有顯式與隱式。

顯式:Java反射或者ClassLoader來動態載入一個類物件。

隱式:類名.方法名()或者new()類的例項。

我們可以自定義類載入器去載入任意的類

// 反射載入TestHelloWorld示例
Class.forName("com.anbai.sec.classloader.TestHelloWorld"); // ClassLoader載入TestHelloWorld示例 this.getClass().getClassLoader().loadClass("com.anbai.sec.classloader.TestHelloWorld");

5.重寫classloader

可以通過重寫classloader類來載入位元組碼(為一個不存在的類),載入到JVM裡面去,然後通過反射去呼叫這個類來例項化他的物件呼叫他的方法。這裡就以TestHelloWorld類為例,先註釋掉之前寫的TestHelloWorld,

完整重寫程式碼

package com.anbai.sec.classloader;

import java.lang.reflect.Method;

/**
 * Creator: yz
 * Date: 2019/12/17
 */
public class TestClassLoader extends ClassLoader {

    // TestHelloWorld類名
    public static String TEST_CLASS_NAME = "com.anbai.sec.classloader.TestHelloWorld";

    // TestHelloWorld類位元組碼
    public static byte[] TEST_CLASS_BYTES = new byte[]{
            -54, -2, -70, -66, 0, 0, 0, 51, 0, 17, 10, 0, 4, 0, 13, 8, 0, 14, 7, 0, 15, 7, 0,
            16, 1, 0, 6, 60, 105, 110, 105, 116, 62, 1, 0, 3, 40, 41, 86, 1, 0, 4, 67, 111, 100,
            101, 1, 0, 15, 76, 105, 110, 101, 78, 117, 109, 98, 101, 114, 84, 97, 98, 108, 101,
            1, 0, 5, 104, 101, 108, 108, 111, 1, 0, 20, 40, 41, 76, 106, 97, 118, 97, 47, 108,
            97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 1, 0, 10, 83, 111, 117, 114, 99,
            101, 70, 105, 108, 101, 1, 0, 19, 84, 101, 115, 116, 72, 101, 108, 108, 111, 87, 111,
            114, 108, 100, 46, 106, 97, 118, 97, 12, 0, 5, 0, 6, 1, 0, 12, 72, 101, 108, 108, 111,
            32, 87, 111, 114, 108, 100, 126, 1, 0, 40, 99, 111, 109, 47, 97, 110, 98, 97, 105, 47,
            115, 101, 99, 47, 99, 108, 97, 115, 115, 108, 111, 97, 100, 101, 114, 47, 84, 101, 115,
            116, 72, 101, 108, 108, 111, 87, 111, 114, 108, 100, 1, 0, 16, 106, 97, 118, 97, 47, 108,
            97, 110, 103, 47, 79, 98, 106, 101, 99, 116, 0, 33, 0, 3, 0, 4, 0, 0, 0, 0, 0, 2, 0, 1,
            0, 5, 0, 6, 0, 1, 0, 7, 0, 0, 0, 29, 0, 1, 0, 1, 0, 0, 0, 5, 42, -73, 0, 1, -79, 0, 0, 0,
            1, 0, 8, 0, 0, 0, 6, 0, 1, 0, 0, 0, 7, 0, 1, 0, 9, 0, 10, 0, 1, 0, 7, 0, 0, 0, 27, 0, 1,
            0, 1, 0, 0, 0, 3, 18, 2, -80, 0, 0, 0, 1, 0, 8, 0, 0, 0, 6, 0, 1, 0, 0, 0, 10, 0, 1, 0, 11,
            0, 0, 0, 2, 0, 12
    };

    @Override
    public Class<?> findClass(String name) throws ClassNotFoundException {
        // 只處理TestHelloWorld類
        if (name.equals(TEST_CLASS_NAME)) {
            // 呼叫JVM的native方法定義TestHelloWorld類
            return defineClass(TEST_CLASS_NAME, TEST_CLASS_BYTES, 0, TEST_CLASS_BYTES.length);
        }

        return super.findClass(name);
    }

    /**
     * 使用自定義類載入器載入TestHelloWorld類位元組碼並呼叫hello方法示例,等價於如下程式碼:
     * <p>
     *
     * </p>
     *
     * @param args
     */
    public static void main(String[] args) {
        // 建立自定義的類載入器
        TestClassLoader loader = new TestClassLoader();

        try {
            // 使用自定義的類載入器載入TestHelloWorld類
            Class testClass = loader.loadClass(TEST_CLASS_NAME);

            // 反射建立TestHelloWorld類,等價於 TestHelloWorld t = new TestHelloWorld();
            Object testInstance = testClass.newInstance();

            // 反射獲取hello方法
            Method method = testInstance.getClass().getMethod("hello");

            // 反射呼叫hello方法,等價於 String str = t.hello();
            String str = (String) method.invoke(testInstance);

            System.out.println(str);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

首先繼承ClassLoader 重寫他的方法,這裡定義了TEST_CLASS_NAME就是TestHelloWorld的包名到名字

看這個findClass,做一個判斷,名字是否是我們所寫的TestHelloWorld,然後return defineClass

是呼叫JVM的方法去定義TestHellowWorld類;

如果不是我們想建立的物件就

return super.findClass(name);

用super呼叫父類的findclass去建立。

這裡程式碼的main方法就是這樣的流程,注意看註釋

建立自定義的類載入器loader物件-->用我們重寫的類載入器去載入TestHelloWorld類

-->然後通過反射該類,獲取對應物件-->然後反射呼叫物件來invoke呼叫到hello方法

-->最後通過hello方法的返回str也就是hello world 輸出

/**
     * 使用自定義類載入器載入TestHelloWorld類位元組碼並呼叫hello方法示例,等價於如下程式碼:
     * <p>
     *
     * </p>
     *
     * @param args
     */
    public static void main(String[] args) {
        // 建立自定義的類載入器
        TestClassLoader loader = new TestClassLoader();

        try {
            // 使用自定義的類載入器載入TestHelloWorld類
            Class testClass = loader.loadClass(TEST_CLASS_NAME);

            // 反射建立TestHelloWorld類,等價於 TestHelloWorld t = new TestHelloWorld();
            Object testInstance = testClass.newInstance();

            // 反射獲取hello方法
            Method method = testInstance.getClass().getMethod("hello");

            // 反射呼叫hello方法,等價於 String str = t.hello();
            String str = (String) method.invoke(testInstance);

            System.out.println(str);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

ok來打斷點走一遍整個呼叫流程首先是在載入我們的TestHelloWorld類


然後以為名字是if判斷對應的名字,所以會進入到defineClass方法裡面

defineClass是會返回一個物件的


我們用的是58行斷點那裡的testClass來接收這個返回值

這個返回值也就是TestHelloWorld的物件了,然後我們通過反射去呼叫這個物件

然後通過testInstance來反射獲取到Hello方法

最後就是通過反射呼叫hello方法來執行,返回給str 輸出了hello world:



剛開始理解起來確實有些難,慢慢來把,學習之路,慢就是快~