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
ClassLoader
類有如下核心方法:
loadClass
(載入指定的Java類)findClass
(查詢指定的Java類)findLoadedClass
(查詢JVM已經載入過的類)defineClass
(定義一個Java類)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:
剛開始理解起來確實有些難,慢慢來把,學習之路,慢就是快~