1.概述
classloader 的作用是加載字節碼到jvm,有些情況下,我們比如使用插件模式,可能需要自定義從外部加載插件到jvm。這裏還有一個需要注意的是java的雙親委派機制。
2.實現過程
2.1.定義自定義classloader
package com.example.demo.loader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class MyClassLoader extends ClassLoader {
private String classPath;
public MyClassLoader(String classPath) {
this.classPath = classPath;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] classData = loadClassData(name);
if (classData == null) {
throw new ClassNotFoundException();
} else {
return defineClass(name, classData, 0, classData.length);
}
}
private byte[] loadClassData(String className) {
// 將包名轉換為路徑
String fileName = classPath + File.separator +
className.replace('.', File.separatorChar) + ".class";
try (InputStream is = new FileInputStream(fileName);
ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
int ch;
while ((ch = is.read()) != -1) {
baos.write(ch);
}
return baos.toByteArray();
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
}
這個代碼繼承 classloader,並重寫了findClass.
2.2.定義外部類
package com.example.demo.loader;
public class MyDynamicClass {
public void sayHello() {
System.out.println("Hello from dynamically loaded class!");
}
}
2.3. 使用自定義classloader
package com.example.demo.loader;
public class ClassLoaderDemo {
public static void main(String[] args) {
try {
// 指向 .class 文件的根目錄
MyClassLoader loader = new MyClassLoader("D:\\work\\research\\demo\\target\\classes");
// 加載類(注意使用全限定名)
Class<?> clazz = loader.loadClass("com.example.demo.loader.MyDynamicClass");
// 創建實例
Object instance = clazz.getDeclaredConstructor().newInstance();
// 調用方法
clazz.getMethod("sayHello").invoke(instance);
} catch (Exception e) {
e.printStackTrace();
}
}
}
這樣我們可以看到 MyDynamicClass 的輸出。
Hello from dynamically loaded class
這個時候,如果我將這個 MyDynamicClass 拷貝到D:\temp\class 目錄下。
我們更改代碼:
MyClassLoader loader = new MyClassLoader("D:\temp\class");
這個時候,我在修改一下MyDynamicClass 的輸出
package com.example.demo.loader;
public class MyDynamicClass {
public void sayHello() {
System.out.println("測試一下輸出!");
}
}
這個時候在ClassLoaderDemo執行一下,我們發現輸出是"測試一下輸出!",並沒有使用D:\temp\class 下的class
這裏涉及到一個 java的雙親委派機制。
2.3 什麼是雙親委派機制
雙親委派機制是Java類加載器的一種工作模式,當一個類加載器收到類加載請求時,它首先不會自己去嘗試加載這個類,而是把請求委託給父類加載器去完成,依次向上遞歸,只有當父類加載器無法完成加載時,子加載器才會嘗試自己加載。
2.4 如果不想使用雙親機制
我們的類可以這麼修改一下
Class<?> clazz = loader.findClass("com.example.demo.loader.MyDynamicClass");
這個就是沒有走 loadClass 方法,直接調用 findClass方法,獲取到外部類。
2.5 雙親委派機制的實現原理代碼
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
// 首先,檢查請求的類是否已經被加載過
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
// 如果父加載器不為空,則委託給父加載器加載
c = parent.loadClass(name, false);
} else {
// 如果父加載器為空,則委託給啓動類加載器加載
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// 父加載器無法加載,拋出ClassNotFoundException
}
if (c == null) {
// 如果父加載器無法加載,則調用自己的findClass方法進行加載
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
從這個代碼我們就可以看出,因為 在classpath下已經找到了 實現類,所以就不會在使用自定義classload加載的類。
如果直接從class path 下刪除哪個 MyDynamicClass class 文件,我們可以看到自定義 classloader是生效的。