目錄
一、作用
- 實現類加載的功能
- 確定被加載類 在 Java虛擬機中 的 唯一性
下面我會進行詳細講解。
1、實現類加載的功能
即實現 類加載過程中“加載”環節裏 “通過類的全限定名來獲取定義此類的二進制字節流” 的功能
具體請看:(JVM)Java虛擬機:類的生命週期(類加載的5個過程)詳解
2、確立 被加載類 在 Java虛擬機中 的 唯一性
- 確定 兩個類是否 相等 的依據:是否由同一個類加載器加載
- 若 由同一個類加載器 加載,則這兩個類相等;
- 若 由不同的類加載器 加載,則這兩個類不相等。 即使兩個類來源於同一個 Class 文件、被同一個虛擬機加載,這兩個類都不相等
- 在實際使用中,是通過下面方法的返回結果(Boolean值)進行判斷:
- Class對象的equals()方法
- Class對象的isAssignableFrom()方法
- Class對象的isInstance()方法
- 當然也會使用instanceof關鍵字做對象所屬關係判定等情況
- 實例說明
下面我將舉個例子來說明:
public class Test {
// 自定義一個類加載器:myLoader
// 作用:可加載與自己在同一路徑下的Class文件
static ClassLoader myLoader = new ClassLoader() {
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
if (!name.equals("com.carson.Test"))
return super.loadClass(name);
try {
String fileName = name.substring(name.lastIndexOf(".") + 1)
+ ".class";
InputStream is = getClass().getResourceAsStream(fileName);
if (is == null) {
return super.loadClass(fileName);
}
byte[] b = new byte[is.available()];
is.read(b);
return defineClass(name, b, 0, b.length);
} catch (IOException e) {
throw new ClassNotFoundException(name);
}
}
};
public static void main(String[] args) throws Exception {
Object obj = myLoader.loadClass("com.carson.Test");
// 1. 使用該自定義類加載器加載一個名爲com.carson.Test的類
// 2. 實例化該對象
System.out.println(obj);
// 輸出該對象的類 ->>第一行結果分析
System.out.println(obj instanceof com.carson.Test);
// 判斷該對象是否屬於com.carson.Test類 ->>第二行結果分析
}
}
<-- 輸出結果 -->
class com.carson.Test
false
// 第一行結果分析
// obj對象確實是com.carson.Test類實例化出來的對象
// 第二行結果分析
// obj對象與類com.huachao.Test做所屬類型檢查時卻返回了false
// 原因:虛擬機中存在了兩個Test類(1 & 2):1是由系統應用程序類加載器加載的,2是由我們自定義的類加載器加載
// 雖然都是來自同一個class文件,但由於由不同類加載器加載,所以依然是兩個獨立的類
// 做對象所屬類型檢查結果自然爲false。
二、類加載器的類型
- 類加載器的類型數量分別從 Java虛擬機 & Java開發者的角度來看,如下圖
- 下面主要講解從 Java 開發者角度看的類加載器,即講解:
- 啓動類加載器
- 擴展類加載器
- 應用程序類加載器
1、啓動類加載器(Bootstrap ClassLoader)
- 作用
負責加載以下類:
- 存放在<JAVA_HOME>\lib目錄中的類
- 被-Xbootclasspath參數所指定路徑中、並且是被虛擬機識別的類庫。 僅按文件名識別,如:rt.jar,名字不符合的類庫即使放在lib目錄中也不會被加載
- 特別注意
- 啓動類加載器 無法 被Java程序直接引用
- 用戶在編寫自定義類加載器時,若需把 加載請求 委派 給 引導類加載器,直接使用null代替即可
如java.lang.ClassLoader.getClassLoader()
方法所示:
@CallerSensitive
public ClassLoader getClassLoader() {
ClassLoader cl = getClassLoader0();
if (cl == null)
return null;
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
ClassLoader.checkClassLoaderPermission(cl, Reflection.getCallerClass());
}
return cl;
}
2、擴展類加載器(Extension ClassLoader)
- 作用:
負責加載以下類:
- <JAVA_HOME>\lib\ext目錄中的類庫
- 被java.ext.dirs系統變量所指定的路徑中的所有類庫
- 特別注意
- 由
sum.misc.Launcher$ExtClassLoader
類實現- 開發者可以直接使用擴展類加載器
3、應用程序類加載器(Application ClassLoader)
- 作用:
負責加載 用戶類路徑(
ClassPath
)上所指定的類庫
- 特別注意
- 也稱爲系統類加載器,因爲該類加載器是ClassLoader中的getSystemClassLoader()方法的返回值
- 由sum.misc.Launcher$AppClassLoader類實現
- 開發者可以直接使用該類加載器
- 若開發者 沒 自定義類加載器,程序默認使用該類加載器
- 各種類加載器的使用並不是孤立的,而是相互配合使用
- 在Java虛擬機中,各種類加載器 配合使用 的 模型(關係)是 雙親委派模型
下面我將詳細講解。
三、雙親委派模型
1、模型說明
2、工作流程講解
雙親委派模型的工作流程代碼實現在java.lang.ClassLoader的loadClass()
中具體如下:
@Override
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
Class<?> c = findLoadedClass(name);
// 檢查需要加載的類是否已經被加載過
if (c == null) {
try {
// 若沒有加載,則調用父加載器的loadClass()方法
if (parent != null) {
c = parent.loadClass(name, false);
}else{
// 若父類加載器爲空,則默認使用啓動類加載器作爲父加載器
c=findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// 若父類加載器加載失敗會拋出ClassNotFoundException,
//說明父類加載器無法完成加載請求
}
if(c==null){
// 在父類加載器無法加載時
// 再調用本身的findClass方法進行類加載
c=findClass(name);
}
}
if(resolve){
resolveClass(c);
}
return c;
}
步驟總結:若一個類加載器收到了類加載請求
- 把 該類加載請求 委派給 父類加載器去完成,而不會自己去加載該類
每層的類加載器都是如此,因此所有的加載請求最終都應傳送到頂層的啓動類加載器中
- 只有當 父類加載器 反饋 自己無法完成該加載請求(它的搜索範圍中沒有找到所需的類)時,子加載器纔會自己去加載
3、優點
Java類隨着它的類加載器一起具備了一種帶優先級的層次關係
- 如:類 java.lang.Object(存放在rt.jar中)在加載過程中,無論哪一個類加載器要加載這個類,最終需委派給模型頂端的啓動類加載器進行加載,因此Object類在程序的各種類加載器環境中都是同一個類。
- 若沒有使用雙親委派模型(即由各個類加載器自行去加載)、用戶編寫了一個java.lang.Object的類(放在ClassPath中),那系統中將出現多個不同的Object類,Java體系中最基礎的行爲就無法保證
在講完系統的類加載器後,下面我將講解如何根據需求自定義類加載器。
四、自定義類加載器
主要是通過繼承自ClassLoader類 從而自定義一個類加載器
MyClassLoader.java
// 繼承自ClassLoader類
public class MyClassLoader extends ClassLoader {
// 類加載器的名稱
private String name;
// 類存放的路徑
private String classpath = "E:/";
MyClassLoader(String name) {
this.name = name;
}
MyClassLoader(ClassLoader parent, String name) {
super(parent);
this.name = name;
}
@Override
public Class<?> findClass(String name) {
byte[] data = loadClassData(name);
return this.defineClass(name, data, 0, data.length);
}
public byte[] loadClassData(String name) {
try {
name = name.replace(".", "//");
System.out.println(name);
FileInputStream is = new FileInputStream(new File(classpath + name
+ ".class"));
byte[] data = new byte[is.available()];
is.read(data);
is.close();
return data;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
下面我將用一個實例來說明如何自定義類加載器 & 使用。
步驟1:自定義類加載器MyClassLoader
MyClassLoader.java
// 繼承自ClassLoader類
public class MyClassLoader extends ClassLoader {
// 類加載器的名稱
private String name;
// 類存放的路徑
private String classpath = "E:/";
MyClassLoader(String name) {
this.name = name;
}
MyClassLoader(ClassLoader parent, String name) {
super(parent);
this.name = name;
}
@Override
public Class<?> findClass(String name) {
byte[] data = loadClassData(name);
return this.defineClass(name, data, 0, data.length);
}
public byte[] loadClassData(String name) {
try {
name = name.replace(".", "//");
System.out.println(name);
FileInputStream is = new FileInputStream(new File(classpath + name
+ ".class"));
byte[] data = new byte[is.available()];
is.read(data);
is.close();
return data;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
步驟2:定義待加載的類
TestObject.java
public class TestObject {
public void print() {
System.out.println("hello DiyClassLoader");
}
}
步驟3:定義測試類
Test.java
public class Test {
public static void main(String[] args) throws InstantiationException,
IllegalAccessException, ClassNotFoundException {
MyClassLoader cl = new MyClassLoader("myClassLoader");
// 步驟1:創建自定義類加載器對象
Class<?> clazz = cl.loadClass("com.carson.TestObject");
// 步驟2:加載定義的測試類:myClassLoader類
TestObject test= (TestObject) clazz.newInstance();
// 步驟3:獲得該類的對象
test.print();
// 輸出
}
}
// 輸出結果
hello DiyClassLoader