一、概述
1.類加載器的定義
所有Java類在使用的時候都必須通過類加載器加載到內存。Java虛擬機可以安裝多個類加載器,系統默認的,有三個主要的類加載器:BootStrap
, ExtClassLoader
, AppClassLoader
。它們分別負責加載特定位置的類。
2.類加載器的性質
類加載器也是Java類。因此,類加載器也要被一個類加載器加載,才能工作。這麼說來,必須有一個不是Java類的類加載,才能完成上述工作。這個類加載器就是BootStrap
,它是所有類加載器的父類。
3.類加載器的繼承關係
Java虛擬機中的所有類加載器都具有其父子關係的樹形結構,在實例化每個加載器的對象時,需要爲其指定一個父級類加載器對象或者默認採用系統類加載器爲其父級類加載器。
二、委託機制
在類加載器的委託機制中,子類類加載器都會先委託其父類類加載器去尋找要加載的類;如果父類加載器尋找到該類,就直接加載該類;如果沒有找到,就會逐級返回,直到回到子類加載器,由子類類加載器加載該類。
三、自定義類加載器
自定義類加載器必須繼承ClassLoader
類,並複寫findClass()
方法。
提示:沒有複寫loadClass()方法的原因在於,loadClass()方法會找其父類加載器,如果父類加載器無法完成類的加載,loadClass()方法回返回來調用findClass()方法。如果複寫loadClass()方法,那麼去調用父類的實現還需要自己動手寫,代碼複雜。所以只需要複寫findClass()方法即可。
下面的例子實現了用自定義類加載器加載一個類,並將這個類進行加密解密,也就是說,只有自定義類加載器才能調用該類。
需要被加密的類:
package com.heisejiuhuche.javaenhance;
import java.util.Date;
/**
* 由於這個類是要被加密的,一旦類名出現在程序中,編譯無法通過
* 所以採取繼承一個類的形式來調用這個類。
* @author jeremy
*
*/
public class ClassLoaderAttachment extends Date {
/* 繼承Date類,並複寫toString()方法 */
public String toString() {
//一旦類加載成功,調用該類的toString()方法將打印Shit happens...
return "Shit happens...";
}
}
自定義類加載器:
package com.heisejiuhuche.javaenhance;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
/**
* 自定義類加載器
* 這個類可以加載ClassLoaderAttachment類,並完成了對ClassLoaderAttachment類的加密和解密
* @author jeremy
*
*/
public class MyClassLoader extends ClassLoader {
/* 私有成員dir, 用於接收要加載類的路徑 */
private String dir;
/* 無參構造方法 */
public MyClassLoader() {}
/* 接收加載類路徑的構造方法 */
public MyClassLoader(String dir) {
this.dir = dir;
}
/* main方法完成對ClassLoaderAttachment類的加密和解密工作 */
public static void main(String[] args) throws Exception {
String src = args[0];
/* 通過main()方法參數獲取源文件絕對路徑 */
File srcPath = new File(src);
/* 通過main()方法參數拼接目標文件的路徑,這裏是讀取源文件,並存儲到一個指定的目錄下*/
String dest = args[1] + File.separator + srcPath.getName();
File destPath = new File(dest);
/* 獲取輸入輸出流 */
FileInputStream fis = new FileInputStream(srcPath);
FileOutputStream fos = new FileOutputStream(destPath);
/* 對源文件進行加密 */
encrypt(fis, fos);
fis.close();
fos.close();
}
/* 複寫findClass方法 */
@Override
protected java.lang.Class<?> findClass(String arg0) throws ClassNotFoundException {
try {
/* 在loadClass()方法中只傳遞了要加載類不帶後綴的名字,所以這裏要拼接一個文件路徑來加載該類 */
String fileName = dir + File.separator + arg0 + ".class";
FileInputStream fis = new FileInputStream(fileName);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
/* 解密 */
encrypt(fis, bos);
fis.close();
byte[] buf = bos.toByteArray();
/* 返回一個Class */
return defineClass(buf, 0, buf.length);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
};
/* 簡單的加密算法,將讀到的每個字節都取反 */
private static void encrypt(InputStream is, OutputStream os) throws Exception {
int b = 0;
while((b = is.read()) != -1) {
os.write(b ^ 0xff);
}
}
}
類加載器測試類:
package com.heisejiuhuche.javaenhance;
import java.util.Date;
/* 測試類測試自定義類加載器 */
public class ClassLoaderTest {
public static void main(String[] args) throws Exception {
/* 得到解密之後的ClassLoaderAttachment類的字節碼 */
Class clazz = new MyClassLoader("heisejiuhuchelib").loadClass("ClassLoaderAttachment");
/* 由於ClassLoaderAttachment類是加密的,不能直接調用,所以創建其實例對象後向上轉型爲Date類型*/
Date d = (Date)clazz.newInstance();
/* 調用toString()方法,打印Shit happens... */
System.out.println(d.toString());
}
}
程序輸出結果:Shit happens...