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:

 

 

 

 

 



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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM