自定義類加載器的應用場景
加密:如果你不想自己的代碼被反編譯的話。(類加密后就不能再用ClassLoader進行加載了,這時需要自定義一個類加載器先對類進行解密,再加載)。
從非標準的來源加載代碼:如果你的字節(jié)碼存放在數(shù)據(jù)庫甚至是云端,就需要自定義類加載器,從指定來源加載類。
雙親委派
- 我們先看一下
ClassLoader類默認的loadClass方法實現(xiàn)
protected synchronized Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
// First, check if the class has already been loaded
Class c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
//父類加載器無法完成類加載請求
}
if (c == null) {
// If still not found, then invoke findClass in order to find the class
//子加載器進行類加載
c = findClass(name);
}
}
if (resolve) {
//判斷是否需要鏈接過程,參數(shù)傳入
resolveClass(c);
}
return c;
}
雙親委派模型工作過程如下:
(1) 類加載器從已加載的類中查詢該類是否已加載,如果已加載則直接返回。
(2)如果在已加載的類中未找到該類,則委托給父類加載器去加載c = parent.loadClass(name, false),父類也會采用同樣的策略查看自己加載的類中是否包含該類,如果沒有則委托給父類,以此類推一直到啟動類加載起。
(3)如果啟動類加載器加載失?。ɡ缭?code>$JAVA_HOME/jre/lib里未查找到該class),會使用拓展類加載器來嘗試加載,繼續(xù)失敗則會使用AppClassLoader來加載,繼續(xù)失敗則會拋出一個異常ClassNotFoundException,然后再調用當前加載器的findClass()方法進行加載。
雙親委派的好處:
(1)避免自己編寫的類動態(tài)替換java的核心類,比如String。
(2)避免了類的重復加載,因為JVM區(qū)分不同類的方式不僅僅根據(jù)類名,相同的class文件被不同的類加載器加載產(chǎn)生的是兩個不同的類。
正題:自定義類加載器
從上面的源碼可以看出調用classLoader時會先根據(jù)委派模型在父類加在其中加載,如果加載失敗則會加載當前加載器的findClass方法來加載,
因此我們自定義的類加載器只需要繼承ClassLoader,并覆蓋findClass方法。
- 準備一個
class文件,編譯后放到D盤根目錄下
public class People {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
- 自定義類加載器
MyClassLoader,繼承ClassLoader覆蓋findClass方法(其中defineClass方法可以把二進制流字節(jié)組成的文件轉換為一個java.lang.Class)
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.WritableByteChannel;
public class MyClassLoader extends ClassLoader
{
public MyClassLoader(){}
public MyClassLoader(ClassLoader parent)
{
super(parent);
}
protected Class<?> findClass(String name) throws ClassNotFoundException
{
File file = new File("D:/People.class");
try{
byte[] bytes = getClassBytes(file);
//defineClass方法可以把二進制流字節(jié)組成的文件轉換為一個java.lang.Class
Class<?> c = this.defineClass(name, bytes, 0, bytes.length);
return c;
}
catch (Exception e)
{
e.printStackTrace();
}
return super.findClass(name);
}
private byte[] getClassBytes(File file) throws Exception
{
// 這里要讀入.class的字節(jié),因此要使用字節(jié)流
FileInputStream fis = new FileInputStream(file);
FileChannel fc = fis.getChannel();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
WritableByteChannel wbc = Channels.newChannel(baos);
ByteBuffer by = ByteBuffer.allocate(1024);
while (true){
int i = fc.read(by);
if (i == 0 || i == -1)
break;
by.flip();
wbc.write(by);
by.clear();
}
fis.close();
return baos.toByteArray();
}
}
- 在主函數(shù)中測試一下
MyClassLoader mcl = new MyClassLoader();
Class<?> clazz = Class.forName("People", true, mcl);
Object obj = clazz.newInstance();
System.out.println(obj);
//打印出我們的自定義類加載器
System.out.println(obj.getClass().getClassLoader());
參考鏈接:
https://www.cnblogs.com/gdpuzxs/p/7044963.html
https://blog.csdn.net/seu_calvin/article/details/52315125