1坪哄、背景
最近在做項(xiàng)目的過(guò)程中站辉,由于系統(tǒng)需要提供一個(gè)對(duì)外接口呢撞,使系統(tǒng)使用者可以以腳本的形式提交自己的代碼,每個(gè)用戶可以在系統(tǒng)規(guī)范的約束下編寫(xiě)腳本饰剥,由系統(tǒng)去執(zhí)行用戶的代碼,實(shí)現(xiàn)了熱部署摧阅。什么叫熱部署呢汰蓉?簡(jiǎn)單來(lái)說(shuō)就是把代碼當(dāng)成U盤(pán)或者外設(shè)一樣即插即用,每個(gè)用戶可以維護(hù)自己的解決方案(也就是一段腳本棒卷,一個(gè)單獨(dú)的類)顾孽,在更新修改解決方案的過(guò)程中而不需要重新編譯啟動(dòng)整個(gè)系統(tǒng)。我們采用的方案就是GroovyClassLoader比规,我主要講一講自己對(duì)ClassLoader的理解和使用若厚。
2、類加載與類加載器
類加載:
類加載的過(guò)程就是將Class文件中描述的各種信息加載到虛擬機(jī)中蜒什,供程序后期運(yùn)行和使用的测秸。
類加載的生命周期主要分為五個(gè)步驟:
1、加載:
通過(guò)一個(gè)類的全限定名來(lái)獲取描述此類的二進(jìn)制字節(jié)流
將這個(gè)字節(jié)流所代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)化為方法去的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)
在內(nèi)存中生成一個(gè)代表這個(gè)類的java.lang.Class 對(duì)象灾常,作為方法區(qū)的各種數(shù)據(jù)類型的入口
2霎冯、驗(yàn)證
為了確保Class文件的字節(jié)流中包含的信息符合當(dāng)前虛擬機(jī)的要求,并且不會(huì)危害到自身的安全钞瀑。包括文件格式驗(yàn)證沈撞,元數(shù)據(jù)驗(yàn)證,字節(jié)碼驗(yàn)證雕什,符號(hào)引用驗(yàn)證缠俺。
3、準(zhǔn)備
為變量分配內(nèi)存贷岸,設(shè)置類變量的初始值壹士。
4、解析
將常量池中的符號(hào)應(yīng)用替代為直接引用凰盔。
5墓卦、初始化
是類加載生命周期的最后一個(gè)過(guò)程,執(zhí)行類中定義的java程序代碼
該過(guò)程如圖所示:
![0_1319366219Na16.gif](https://private-alipayobjects.alipay.com/alipay-rmsdeploy-image/skylark/gif/34855/28c473160c027e54.gif)
0_1319366219Na16.gif
類加載器:
在前面的類加載過(guò)程中户敬,大部分動(dòng)作都是完全由虛擬機(jī)主導(dǎo)和控制的落剪。而類加載器使得用戶可以在加載的過(guò)程中參與進(jìn)來(lái),結(jié)合前面的內(nèi)容尿庐,類加載器就是將“通過(guò)一個(gè)類的全限定名來(lái)獲取描述此類的二進(jìn)制字節(jié)流”這個(gè)動(dòng)作放到j(luò)ava虛擬機(jī)外部來(lái)實(shí)現(xiàn)忠怖。將主動(dòng)權(quán)交給程序猿。
類加載器和這個(gè)類本身確定了其在java虛擬機(jī)中的唯一性抄瑟,每一個(gè)類加載器都有一個(gè)獨(dú)立的類命名空間凡泣,也就意味著,如果比較兩個(gè)類是否相等,只有在這兩個(gè)類是由同一個(gè)類加載器加載的前提下才有意義鞋拟,否則骂维,即使這兩個(gè)類來(lái)源于同一個(gè)Class文件,被同一個(gè)虛擬機(jī)加載贺纲,只要加載他們的類加載器不同航闺,那么這兩個(gè)類就注定不相同。
java的類加載器:
![0_1319366276S7Uf.gif](https://private-alipayobjects.alipay.com/alipay-rmsdeploy-image/skylark/gif/34855/8f7bfafb8c0edde6.gif)
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è)計(jì)模式,即傳入一個(gè)類限定名,逐層向上到Bootstrap Class Loader中查找,如果找到即返回,若沒(méi)有找到,則在Extention Class Loader中找,若還沒(méi)有找到則在System Class Loader下找,即classpath中,如果還沒(méi)有找到,則調(diào)用findClass(name)方法,執(zhí)行用戶自己的類加載邏輯(可能在其他的地方)
ClassLoader中的幾個(gè)重要的方法:
1澄成、loadClass(String name, boolean resolve):加載類的方法胧洒,在jdk1.2以前需要重寫(xiě)該方法實(shí)現(xiàn)用戶自己的邏輯,1.2以后為了向下兼容环揽,仍然可以重寫(xiě)該方法略荡,但是建議用戶將自己的加載邏輯實(shí)現(xiàn)在findName(name)中。這樣系統(tǒng)先向上尋找能否加載到該類,如果加在不到,將調(diào)用用戶自定義的findName函數(shù)加載對(duì)象.
/**
* @param name 類名字
* @param resolve 是否解析歉胶,如果只是想知道該class是否存在可以設(shè)置該參數(shù)為false
* @return 返回一個(gè)class泛型
* @throws ClassNotFoundException
*/
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
/**
* getClassLoadingLock(name)
* 為類的加載操作返回一個(gè)鎖對(duì)象汛兜。為了向后兼容,這個(gè)方法這樣實(shí)現(xiàn):如果當(dāng)前的classloader對(duì)象注冊(cè)了并行能力通今,
* 方法返回一個(gè)與指定的名字className相關(guān)聯(lián)的特定對(duì)象粥谬,否則,直接返回當(dāng)前的ClassLoader對(duì)象辫塌。
*/
synchronized (getClassLoadingLock(name)) {
// 首先查看class是否已經(jīng)被加載過(guò)
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
//如果父加載器不為空漏策,則委托給父加載器去加載
if (parent != null) {
c = parent.loadClass(name, false);
} else {
/**
* 如果父加載器為空,說(shuō)明父加載器已經(jīng)是Bootstrap ClassLoader了臼氨,則直接使用根加載器加載,也就是使用虛擬機(jī)加
* 載器加載
*/
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
//如果以上的加載器在自己的路徑上面都沒(méi)有加載到,則調(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)類的時(shí)候储矩,可以調(diào)用此方法感耙,不過(guò)在ClassLoader的默認(rèn)實(shí)現(xiàn)中,ClassLoader先判斷父類加載器是否可以加載持隧,然后再調(diào)用用戶自定義的findClass方法即硼。
3、 resolveClass():若resolve參數(shù)為true的時(shí)候屡拨,我們需要調(diào)用該函數(shù)只酥,resolve我們的classLoader褥实。
4、ClassLoader getSystemClassLoader():提供了一個(gè)直接訪問(wèn)系統(tǒng)classloader的方法裂允。
3损离、廢話少說(shuō)上代碼!
下面我將以一個(gè)例子來(lái)闡述如何使用ClassLoader,自定義的ClassLoader將加載被加密的類,而且這個(gè)類存儲(chǔ)的路徑不在ClassPath中藏畅,也不可以被Bootstrap Class Loader和Extention Class Loader加載别厘,在實(shí)際應(yīng)用中,可以是網(wǎng)絡(luò)中傳遞過(guò)來(lái)的加密字節(jié)流哩俭,抑或著是實(shí)現(xiàn)腳本的熱部署操作绷跑。
package com.siyu;
import java.io.*;
public class ClassLoaderTest extends ClassLoader {
//自定義加載器加載該路徑下面的文件
private String directory;
public ClassLoaderTest(String directory) {
this.directory = directory;
}
/**
* 重寫(xiě)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實(shí)現(xiàn)了鏈接階段的驗(yà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é)寫(xiě)入到加密文件中
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加密后存儲(chǔ)到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();
}
}
}