1、背景
最近在做項目的過程中,由于系統(tǒng)需要提供一個對外接口,使系統(tǒng)使用者可以以腳本的形式提交自己的代碼,每個用戶可以在系統(tǒng)規(guī)范的約束下編寫腳本,由系統(tǒng)去執(zhí)行用戶的代碼,實現(xiàn)了熱部署。什么叫熱部署呢?簡單來說就是把代碼當(dāng)成U盤或者外設(shè)一樣即插即用,每個用戶可以維護(hù)自己的解決方案(也就是一段腳本,一個單獨的類),在更新修改解決方案的過程中而不需要重新編譯啟動整個系統(tǒng)。我們采用的方案就是GroovyClassLoader,我主要講一講自己對ClassLoader的理解和使用。
2、類加載與類加載器
類加載:
類加載的過程就是將Class文件中描述的各種信息加載到虛擬機(jī)中,供程序后期運(yùn)行和使用的。
類加載的生命周期主要分為五個步驟:
1、加載:
通過一個類的全限定名來獲取描述此類的二進(jìn)制字節(jié)流
將這個字節(jié)流所代表的靜態(tài)存儲結(jié)構(gòu)轉(zhuǎn)化為方法去的運(yùn)行時數(shù)據(jù)結(jié)構(gòu)
在內(nèi)存中生成一個代表這個類的java.lang.Class 對象,作為方法區(qū)的各種數(shù)據(jù)類型的入口
2、驗證
為了確保Class文件的字節(jié)流中包含的信息符合當(dāng)前虛擬機(jī)的要求,并且不會危害到自身的安全。包括文件格式驗證,元數(shù)據(jù)驗證,字節(jié)碼驗證,符號引用驗證。
3、準(zhǔn)備
為變量分配內(nèi)存,設(shè)置類變量的初始值。
4、解析
將常量池中的符號應(yīng)用替代為直接引用。
5、初始化
是類加載生命周期的最后一個過程,執(zhí)行類中定義的java程序代碼
該過程如圖所示:

0_1319366219Na16.gif
類加載器:
在前面的類加載過程中,大部分動作都是完全由虛擬機(jī)主導(dǎo)和控制的。而類加載器使得用戶可以在加載的過程中參與進(jìn)來,結(jié)合前面的內(nèi)容,類加載器就是將“通過一個類的全限定名來獲取描述此類的二進(jìn)制字節(jié)流”這個動作放到j(luò)ava虛擬機(jī)外部來實現(xiàn)。將主動權(quán)交給程序猿。
類加載器和這個類本身確定了其在java虛擬機(jī)中的唯一性,每一個類加載器都有一個獨立的類命名空間,也就意味著,如果比較兩個類是否相等,只有在這兩個類是由同一個類加載器加載的前提下才有意義,否則,即使這兩個類來源于同一個Class文件,被同一個虛擬機(jī)加載,只要加載他們的類加載器不同,那么這兩個類就注定不相同。
java的類加載器:

0_1319366276S7Uf.gif
1、Bootstrap Class Loader:負(fù)責(zé)加載JAVA_HOME/lib目錄下或-Xbootclasspath指定目錄的jar包;
2、Extention Class Loader:加載JAVA_HOME/lib/ext目錄下的或-Djava.ext.dirs指定目錄下的jar包。
3、System Class Loader:加載classpath或者-Djava.class.path指定目錄下的類或jar包。
ClassLoader各司其職,加載在不同路徑下的class文件,值得注意的是,類加載采用的是雙親委托的設(shè)計模式,即傳入一個類限定名,逐層向上到Bootstrap Class Loader中查找,如果找到即返回,若沒有找到,則在Extention Class Loader中找,若還沒有找到則在System Class Loader下找,即classpath中,如果還沒有找到,則調(diào)用findClass(name)方法,執(zhí)行用戶自己的類加載邏輯(可能在其他的地方)
ClassLoader中的幾個重要的方法:
1、loadClass(String name, boolean resolve):加載類的方法,在jdk1.2以前需要重寫該方法實現(xiàn)用戶自己的邏輯,1.2以后為了向下兼容,仍然可以重寫該方法,但是建議用戶將自己的加載邏輯實現(xiàn)在findName(name)中。這樣系統(tǒng)先向上尋找能否加載到該類,如果加在不到,將調(diào)用用戶自定義的findName函數(shù)加載對象.
/**
* @param name 類名字
* @param resolve 是否解析,如果只是想知道該class是否存在可以設(shè)置該參數(shù)為false
* @return 返回一個class泛型
* @throws ClassNotFoundException
*/
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
/**
* getClassLoadingLock(name)
* 為類的加載操作返回一個鎖對象。為了向后兼容,這個方法這樣實現(xiàn):如果當(dāng)前的classloader對象注冊了并行能力,
* 方法返回一個與指定的名字className相關(guān)聯(lián)的特定對象,否則,直接返回當(dāng)前的ClassLoader對象。
*/
synchronized (getClassLoadingLock(name)) {
// 首先查看class是否已經(jīng)被加載過
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
//如果父加載器不為空,則委托給父加載器去加載
if (parent != null) {
c = parent.loadClass(name, false);
} else {
/**
* 如果父加載器為空,說明父加載器已經(jīng)是Bootstrap ClassLoader了,則直接使用根加載器加載,也就是使用虛擬機(jī)加
* 載器加載
*/
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
//如果以上的加載器在自己的路徑上面都沒有加載到,則調(diào)用findClass(name)調(diào)用用戶自定義的加載器
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
//根據(jù)resolve參數(shù)決定是否解析該類
if (resolve) {
resolveClass(c);
}
return c;
}
}
2、ClassLoader getParent() :可以返回委托的父類加載器。在你自定義加載器找不到相應(yīng)類的時候,可以調(diào)用此方法,不過在ClassLoader的默認(rèn)實現(xiàn)中,ClassLoader先判斷父類加載器是否可以加載,然后再調(diào)用用戶自定義的findClass方法。
3、 resolveClass():若resolve參數(shù)為true的時候,我們需要調(diào)用該函數(shù),resolve我們的classLoader。
4、ClassLoader getSystemClassLoader():提供了一個直接訪問系統(tǒng)classloader的方法。
3、廢話少說上代碼!
下面我將以一個例子來闡述如何使用ClassLoader,自定義的ClassLoader將加載被加密的類,而且這個類存儲的路徑不在ClassPath中,也不可以被Bootstrap Class Loader和Extention Class Loader加載,在實際應(yīng)用中,可以是網(wǎng)絡(luò)中傳遞過來的加密字節(jié)流,抑或著是實現(xiàn)腳本的熱部署操作。
package com.siyu;
import java.io.*;
public class ClassLoaderTest extends ClassLoader {
//自定義加載器加載該路徑下面的文件
private String directory;
public ClassLoaderTest(String directory) {
this.directory = directory;
}
/**
* 重寫findClass,用戶可以做以下的事情
* 1.可以加載boot、ext、system加載器所加載不了的路徑下的文件
* 2.可以解密加密后的class文件
*/
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
//解密密鑰
byte key = (byte) 1;
//加密文件的路徑
String fileName = directory + name + ".class";
File file = new File(fileName);
byte[] decryptedByte = readFromFile(file);
//解密為原始的class文件
for (int i = 0; i < decryptedByte.length; i++) {
decryptedByte[i] = (byte) (decryptedByte[i] ^ key);
}
//defineClass實現(xiàn)了鏈接階段的驗證等
return defineClass(null, decryptedByte, 0, decryptedByte.length);
}
private byte[] readFromFile(File fileName) {
try {
byte[] bytes = null;
FileInputStream fin = new FileInputStream(fileName);
int i;
if ((i = fin.read()) != -1) {
//初始化數(shù)組大小和文件大小一樣
bytes = new byte[fin.available()];
fin.read(bytes);
}
return bytes;
} catch (FileNotFoundException e) {
e.printStackTrace();
return null;
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
private byte[] encrypt(byte[] bytes) {
byte key = (byte) 1;
//依次加密的代碼
for (int i = 0; i < bytes.length; i++) {
bytes[i] = (byte) (bytes[i] ^ key); //利用異或加密
}
return bytes;
}
public void encryptFile(String fileName, String directory) {
try {
String name = fileName.substring(fileName.lastIndexOf("\\") + 1, fileName.length() - 6);
//加密文件的路徑
String destFileName = directory + "encryted" + name + ".class";
//如果加密文件不存在則創(chuàng)建加密文件
File f = new File(destFileName);
if (f == null) {
f.createNewFile();
}
//加密
byte[] encryptedByte = encrypt(readFromFile(new File(fileName)));
FileOutputStream fos = new FileOutputStream(destFileName);
//把加密后的字節(jié)寫入到加密文件中
fos.write(encryptedByte);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
//設(shè)置加密路徑
ClassLoaderTest classLoaderTest=new ClassLoaderTest("C:\\EncryptedClass\\");
//將test.class加密后存儲到EncryptedClass目錄下
classLoaderTest.encryptFile("C:\\Users\\jasonchu.zsy\\IdeaProjects\\BoKeTest\\out\\production\\BoKeTest\\com\\siyu\\test.class"
,"C:\\EncryptedClass\\");
try {
Class<?> t=classLoaderTest.loadClass("encrytedtest");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}