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類有如下核心方法:
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:

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




