JVM的類加載器
剛剛學習JVM的類加載機制的時候,會被教育說JVM的類加載機制需要不同的類加載器。過了很久之后忘記了。現在再復習一下。
為什么需要多個類加載器?
加載器作用是通過類名來獲取二進制字節流。
我們先拋開所有問題,從寫程序的角度來講一個程序應該具有什么?
1、健壯性
2、功能性
3、魯棒性
4、效率性
5、維護性
6、可靠性(安全性)
由此,我們對比JVM。JVM也是一個軟件,也應該基本符合上述的幾個特性。
健壯性和功能性:JVM可以從不同的地方去加載class,比如文件系統,web,FTP等,這就要求JVM屏蔽底層的加載邏輯,只需要提供一個classloard()的接口就行了,客戶端就可以加載類但是卻不用管類加載器到底是怎么實現的。
再說安全性:JVM得保證自有類不遭到破壞(比如java.lang包下的類,這個破壞的原理就是類對同包級別及其子包下的public類具有操作權限)。為了解決這個問題,使用雙親委派機制剛好可以解決。
此外,如果我想在一個JVM中啟動兩個相同的應用的不同版本(不同版本值得是同樣的全限定名的類,但是功能不一樣),怎么辦?假設只有一個類加載器,那么第二次加載該類會失敗,第二個版本的應用還是使用了第一個版本的class,那么第二個應用就無法提供第二個版本類的功能了。所以得需要每個應用都有相互隔離的類加載器,否則第二個應用的類可能會覆蓋第一個應用之前加載的類,從而造成一些意想不到的后果。
每種加載器都有對應的層級來加載某些特定的類,來保證他們之間的安全性。
自定義的類加載器。
package com.stat;
import java.util.Date;
public class MyDate extends Date {
@Override
public String toString() {
return "MyDate : 我的時間。";
}
}
package com.stat;
import java.io.*;
/**
* 自定義類加載器
*/
public class MyClassLoader extends ClassLoader {
String classDir;
public MyClassLoader() {
}
public MyClassLoader(String classDir) {
this.classDir = classDir;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
String classFile = classDir + "/" + name + ".class";
System.out.println("classFile path==" + classFile);
try {
//這個地方我們只是簡單的讀取文件流的方式來獲取byte數組
//其實可以嘗試將class文件加密以后 這里解密 這樣就可以保證
//這種class文件 只有你寫的classloader才能讀取的了。
//其他任何classloader都讀取不了 包括系統的。
byte[] classByte = toByteArray(classFile);
return defineClass(classByte, 0, classByte.length);
} catch (IOException e) {
e.printStackTrace();
}
return super.findClass(name);
}
public static byte[] toByteArray(String filename) throws IOException, FileNotFoundException {
File f = new File(filename);
if (!f.exists()) {
throw new FileNotFoundException(filename);
}
try (ByteArrayOutputStream bos = new ByteArrayOutputStream((int) f.length())) {
BufferedInputStream in = null;
in = new BufferedInputStream(new FileInputStream(f));
int bufSize = 1024;
byte[] buffer = new byte[bufSize];
int len = 0;
while (-1 != (len = in.read(buffer, 0, bufSize))) {
bos.write(buffer, 0, len);
}
return bos.toByteArray();
} catch (IOException e) {
e.printStackTrace();
throw e;
}
}
}
package com.stat;
import java.util.Date;
public class MyClassTest {
public static void main(String[] args)
{
try {
//注意這個路徑:是在target目錄下的,是編譯后的路徑
Class classDate = new MyClassLoader("D:\\javaworkspace\\test02\\test02_module01\\target\\classes\\com\\stat").loadClass("com.stat.MyDate");
Class classDate2 = new MyClassLoader("D:\\javaworkspace\\test02\\test02_module01\\target\\classes\\com\\stat").loadClass("MyDate");
Date date = (Date) classDate.newInstance();
System.out.println("date ClassLoader:"+date.getClass().getClassLoader().getClass().getName());
System.out.println(date);
Date date2 = (Date) classDate2.newInstance();
System.out.println("date2 ClassLoader:"+date2.getClass().getClassLoader().getClass().getName());
System.out.println(date2);
} catch (Exception e1) {
e1.printStackTrace();
}
}
}
輸出結果如下
classFile path==D:\javaworkspace\test02\test02_module01\target\classes\com\stat/MyDate.class
date ClassLoader:sun.misc.Launcher$AppClassLoader
MyDate : 我的時間。
date2 ClassLoader:com.stat.MyClassLoader
MyDate : 我的時間。
可以看到date 的類加載器是AppClassLoader,而date2的類加載器是自定義的
大家可以看到classdate和classDate2 這2個類,我們在用classLoader去加載的時候傳的參數唯一的不同就是前者傳入了完整的包名,而后者沒有。這就導致了前者的classLoader依舊是系統自帶的appclassloader 而后者才是我們自定義的classloader。 原因:
雖然對於classDate和classDate2來說,我們手動指定了她的類加載是我們自定義的myclassloader,但是根據類加載器的規則,我們能用父親的loadclass就肯定不會用自己的,而我們系統類加載器,AppClassLoader要想loadclass成功是需要傳入完整的包名的。所以classDate的構造還是傳入了完整的包名,這就是為啥classDate的加載器還是AppClassLoader,但是classDate2並沒有傳入完整的包名,所以AppClassLoader也是找不到這個CustomDate類的,最后只能交給MyClassLoader這個最底層的,我們自定義的classloader來load