本文主要內(nèi)容
- 類加載器基本概念
- 自定義類加載器
- 類的隔離
- Android類加載器案例
虛擬機類加載機制 文中已經(jīng)對類加載機制詳細(xì)闡述了,這兩天對類的隔離,破壞雙親委托機制等內(nèi)容有了新的理解,同時闡述下Android上類加載器案例。
以雙親委托機制圖鎮(zhèn)樓:

類加載器基本概念
顧名思義,類加載器(class loader)用來加載 Java 類到 Java 虛擬機中。類加載器負(fù)責(zé)讀取 Java 字節(jié)代碼,并轉(zhuǎn)換成 java.lang.Class類的一個實例。
有了Class類實例,就可以通過newInstance方法創(chuàng)建該類的對象。
一般來說,默認(rèn)類加載器為當(dāng)前類的類加載器。比如A類中引用B類,A的類加載器為C,那么B的類加載器也為C。
1、ClassLoader
ClassLoader類是一個抽象類,它定義了類加載器的基本方法。
| 方法 | 說明 |
|---|---|
| getParent() | 返回該類加載器的父類加載器。 |
| loadClass(String name) | 加載名稱為 name的類,返回的結(jié)果是 java.lang.Class類的實例。 |
| findClass(String name) | 查找名稱為 name的類,返回的結(jié)果是 java.lang.Class類的實例。 |
| findLoadedClass(String name) | 查找名稱為 name的已經(jīng)被加載過的類,返回的結(jié)果是 java.lang.Class類的實例。 |
| defineClass(String name, byte[] b, int off, int len) | 把字節(jié)數(shù)組 b中的內(nèi)容轉(zhuǎn)換成 Java 類,返回的結(jié)果是 java.lang.Class類的實例。這個方法被聲明為 final的。 |
來看看 loadClass 方法的代碼:
protected Class<?> loadClass(String name, boolean resolve){
Class c = findLoadedClass(name);
if (c == null) {
if (parent != null) {
//使用父加載器加載此類
c = parent.loadClass(name, false);
}
if (c == null) {
// 如果父加載器沒有成功加載,則自己嘗試加載
c = findClass(name);
}
}
return c;
}
這段代碼定義了雙親委托模型。所以自定義類加載器盡量不要去重寫 loadClass ,而應(yīng)該重寫 findClass 方法。下邊讓我們來實現(xiàn)一個自定義類加載器。
自定義類加載器
自定義類加載器還是很有必要的,尤其是在web服務(wù)器上,比如tomcat,自定義加載器能夠指定自身加載類的范圍,甚至通過繼承關(guān)系,達(dá)到類的隔離目的。
Android上也有自定義類加載器,Android上的策略是,每個apk都由不同的類加載器實例來加載。思考一下,如果兩個apk中有相同名字的類,如果由同一個類加載器實例來加載,那肯定會混淆。另一方面也是安全問題。
通過前一章的學(xué)習(xí),可知自定義類加載器一般只重寫findClass方法即可。真正完成類的加載工作是通過調(diào)用 defineClass來實現(xiàn)的;而啟動類的加載過程是通過調(diào)用 loadClass來實現(xiàn)的。
public class FileSystemClassLoader extends ClassLoader {
private String rootDir;
public FileSystemClassLoader(String rootDir) {
this.rootDir = rootDir;
}
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] classData = getClassData(name);
if (classData == null) {
throw new ClassNotFoundException();
}
else {
return defineClass(name, classData, 0, classData.length);
}
}
private byte[] getClassData(String className) {
String path = classNameToPath(className);
try {
InputStream ins = new FileInputStream(path);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int bufferSize = 4096;
byte[] buffer = new byte[bufferSize];
int bytesNumRead = 0;
while ((bytesNumRead = ins.read(buffer)) != -1) {
baos.write(buffer, 0, bytesNumRead);
}
return baos.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
private String classNameToPath(String className) {
return rootDir + File.separatorChar
+ className.replace('.', File.separatorChar) + ".class";
}
}
類的隔離
類的隔離主要有以下兩點:
- 不同的類加載器為相同名稱的類創(chuàng)建了額外的名稱空間,不同類加載器加載的類是不兼容的。
- 為了安全或其它目的,使某個模塊無法加載某個類。
第1點比較簡單,網(wǎng)上有很多這方面的文章,在此不再闡述。
第2點稍等復(fù)雜點,以tomcat為例,tomcat服務(wù)器所用到的jar包不希望web應(yīng)用應(yīng)用,于是tomcat設(shè)計了以下經(jīng)典的類加載模型:

其中webApp應(yīng)用的類加載器為 WebApp類加載器,而tomcat服務(wù)器類加載器為 Catalina類加載器,如果webApp想加載tomcat所使用的jar包,它先會委托它的父加載器去加載,根據(jù)上圖所示,它的父加載器也無法加載(因為tomcat所引用jar包全由Catalina加載,而Catalina并不是WebApp的父加載器),由于它自己也無法加載,所以實現(xiàn)隔離。
在雙親委托機制下,同級的類加載器,可以實現(xiàn)類的隔離。
另外,頂層類加載器限制較大,有時它無法加載類也無法委托子類加載器去加載,也會導(dǎo)致隔離。
1、線程上下文類加載器
JDBC是Java開發(fā)者經(jīng)常遇到的內(nèi)容,它的核心類為java.sql.DriverManager,查看它的包名,就知道此類是由 啟動類加載器(Bootstrap ClassLoader)加載,但JDBC的具體驅(qū)動實現(xiàn)是由各個廠商自己實現(xiàn)的。DriverManager需要調(diào)用由廠商自己實現(xiàn)的接口,這些接口是由 用戶程序類加載器(Application ClassLoader)加載。
由前文知,DriverManager由Bootstrap加載,當(dāng)DriverManager引用廠商實現(xiàn)的JDBC接口時,DriverManager仍然會使用自己的類加載器,也就是Bootstrap去加載,但Bootstrap只能加載JAVA_HOME下的class文件,廠商實現(xiàn)的JDBC接口,Bootstrap無法加載。這種問題,雙親委托模型已經(jīng)無法解決了。
為了解決此問題,Java開了后門,也就是添加了線程上下文類加載器,Java為每個線程設(shè)置了默認(rèn)的線程上下文類加載器:Application ClassLoader,當(dāng)出現(xiàn)上述情況時,直接使用線程上下文類加載器加載。
try {
loader = AppClassLoader.getAppClassLoader(extcl);
} catch (IOException e) {
throw new InternalError(
"Could not create application class loader");
}
// Also set the context class loader for the primordial thread.
Thread.currentThread().setContextClassLoader(loader);
在JDBC的例子中,DriverManager不再使用自己的類加載器(Bootstrap)去加載,而是使用線程上下文類加載器去加載,而線程上下文類加載器就是 用戶程序類加載器(Application ClassLoader),這當(dāng)然能加載類成功。
這種父類加載器請求子類加載器去加載類的行為,實質(zhì)上已經(jīng)破壞了雙親委托模型。
2、Class.forName
JDBC在使用之前,一定要調(diào)用一句話,Class.forName,很多人告訴我,這是要去加載驅(qū)動類。
Class.forName("com.mysql.jdbc.Driver");
仔細(xì)想一想,這不對,如果代碼引用了某個類,會去自動加載類,不需要用戶手動加載,除非是當(dāng)前的類加載器無法加載此類。
查看Driver類的源碼:
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
//
// Register ourselves with the DriverManager
//
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
}
類的加載,會有一個初始化階段,在初始化階段會執(zhí)行類的 clinit 方法,也就是會執(zhí)行類的靜態(tài)語句塊。
通過以上線索發(fā)現(xiàn),其實調(diào)用 Class.forName并不是要加載驅(qū)動類,而是調(diào)用驅(qū)動類的靜態(tài)語句塊,向DriverManager注冊自己而已。
Android類加載器案例
Android apk動態(tài)加載研究 文中提到了Context的類加載器與當(dāng)前apk的類加載器相同,其實這句話是不對的,只是Context類重寫了getClassLoader方法。
public ClassLoader getClassLoader() {
return mPackageInfo != null ?
mPackageInfo.getClassLoader() : ClassLoader.getSystemClassLoader();
}
并不是Context的類加載器不同,而是通過LoadedApk獲取的類加載器不同。
mClassLoader = ApplicationLoaders.getDefault().getClassLoader(zip,
mApplicationInfo.targetSdkVersion, isBundledApp, librarySearchPath,
libraryPermittedPath, mBaseClassLoader);
LoadedApk的getClassLoader方法中,根據(jù)apk的路徑等參數(shù),生成了新的ClassLoader,以確保不同的apk對應(yīng)著不同的ClassLoader。
PathClassLoader pathClassloader = PathClassLoaderFactory.createClassLoader(
zip,
librarySearchPath,
libraryPermittedPath,
parent,
targetSdkVersion,
isBundled);
比如說,在ActivityThread類中,生成新的Activity時,就使用了新生成的Classloader,確保不同apk的Activity是由不同Classloader生成的。
Activity activity = null;
try {
java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
StrictMode.incrementExpectedActivityCount(activity.getClass());
r.intent.setExtrasClassLoader(cl);
r.intent.prepareToEnterProcess();
if (r.state != null) {
r.state.setClassLoader(cl);
}
} catch (Exception e) {
。。。
}
newActivity的代碼如下:
public Activity newActivity(ClassLoader cl, String className,
Intent intent)
throws InstantiationException, IllegalAccessException,
ClassNotFoundException {
return (Activity)cl.loadClass(className).newInstance();
}
其實就是調(diào)用ClassLoader 加載具體Activity全類名,然后調(diào)用newInstance生成一個新的對象。
閱讀源碼往往有意想不到的收獲,當(dāng)時懷疑為啥Context的類加載器不一樣,一讀源碼,收獲還挺多的,大家遇到疑問多多讀源碼吧。