編者按
???????? 筆者最近在為WebSphere中的項目引入第三方j(luò)ar的過程中遇到一些阻礙。雖然根據(jù)網(wǎng)上的資料解決了問題,但忽然覺得對Java類加載器的認(rèn)識知之甚少,只停留在和反射的配合使用上,遂決定一探究竟。于是有了這篇文章,即備日后查詢又可饗讀者。水平有限,如有紕漏還望大神不吝賜教。
目錄
一、 之乎者也:什么是 Class Loader ?
二、 格物致知:驗證 Class Loader 的加載方式
三、 切磋琢磨:一個簡單的自定義 Class Loader
四、 他山之石:其他框架中的自定義 Class Loader(未完待續(xù))
五、 別有洞天:關(guān)于Class Loader的相關(guān)拓展(未完待續(xù))
一、 之乎者也:什么是 Class Loader ?
1. 簡明定義與說明
- ????作為Java運(yùn)行時環(huán)境的一部分,Java類加載器動態(tài)地加載Java類到Java虛擬機(jī)中。在Java中,庫通常被打包在Jar文件中。庫中可以包含不同種類的對象, 而在Jar文件中最重要的一種對象類型就是Java類文件(Java class),類文件可以視為一段被命名的代碼。類加載器負(fù)責(zé)定位庫,讀取其中的內(nèi)容并加載庫中的類。加載過程一般是按需進(jìn)行地, 即只有類被調(diào)用時加載過程才會發(fā)生。一個被命名的類只能由指定的類加載器加載一次。每一個類又必須由一個類加載器所加載。
2. Java虛擬機(jī)啟動時使用的Class Loader
| 序號 | 類型 | 查找范圍 |
|---|---|---|
| 1 | Bootstrap class loader | %JAVA_HOME%/jre/lib %JAVA_HOME%/jre/classes -Xbootclasspath1 |
| 2 | Extensions class loader | %JAVA_HOME%/jre/lib/ext java.ext.dirs |
| 3 | System class loader | CLASSPATH -classpath Mainfest |
3. 雙親委托
當(dāng)前Class Loader首先從自己已經(jīng)加載的類中查詢是否此類已經(jīng)加載,如果已經(jīng)加載則直接返回原來已經(jīng)加載的類。每個類加載器都有自己的加載緩存,當(dāng)一個類被加載了以后就會放入緩存,等下次加載的時候就可以直接返回了。
當(dāng)前Class Loader的緩存中沒有找到被加載的類的時候,委托父類加載器去加載,父類加載器采用同樣的策略,首先查看自己的緩存,然后委托父類的父類去加載,直到bootstrap Class Loader。
當(dāng)所有的父類加載器都沒有加載的時候,再由當(dāng)前的類加載器加載,并將其放入它自己的緩存中,以便下次有加載請求的時候直接返回。
通過自定義Class Loader(具體指重載loadClass方法),Java推薦的雙親委托機(jī)制可以被打破。
二、 格物致知:驗證 Class Loader 的加載方式
驗證上述三個Class Loader的存在并建立對雙親委托機(jī)制的初步認(rèn)識
1. 準(zhǔn)備工作
2.1.1 試驗代碼
//D:/ClassLoaderTest.java
// 如果在IDE中編輯,可能會提示IntMath找不到,無視
import com.google.common.math.IntMath;
public class ClassLoaderTest {
public static void main(String[] args) {
logClassLoader( IntMath.class);
}
public static <T> void logClassLoader(Class<T> tClass) {
System.out.println(
String.format(
"打印類 %s 的類加載器鏈:", tClass.getCanonicalName()));
ClassLoader classLoader = tClass.getClassLoader();
while (classLoader!=null) {
System.out.println(
String.format(
"類加載器鏈:%s", classLoader.toString()));
classLoader = classLoader.getParent();
}
System.out.println( "類加載器鏈: Bootstrap class loader");
}
}
2.1.2 第三方Jar包位置
D:\repo\com\google\guava\guava\11.0.2\guava-11.0.2.jar
2.1.3 編譯
javac -classpath ./;D:\repo\com\google\guava\guava\11.0.2\guava-11.0.2.jar
-encoding utf-8 .\ClassLoaderTest.java
2. 運(yùn)行結(jié)果
????????這里直接使用Java命令的參數(shù)來控制類加載器的搜索路徑, 也可以通過其他方法指定, 詳見 附 中 2. 通過其他方式指定類加載器的搜索路徑 一節(jié)
三、 切磋琢磨:一個簡單的自定義 Class Loader
1. 準(zhǔn)備知識
| 關(guān)鍵方法 | 描述 |
|---|---|
| loadClass:(name:String, resolve:boolean)=>Class<?> | 用資源名稱加載Class |
| findClass:(name:String)=>Class<?> | 用資源名稱查找Class |
| defineClass:(name:String, b:byte[], off:int, len:int)=>Class<?> | 轉(zhuǎn)換字節(jié)數(shù)組到一個Class的實例 |
- 實施步驟
- 繼承ClassLoader
- 如果要修改雙親委托的代理方式,重寫loadClass方法
- 重寫findClass方法
2. 準(zhǔn)備工作
3.2.1 試驗代碼
//D:/Main.java
import java.lang.reflect.Method;
public class Main {
public static void main(String[] args) throws Exception {
CustomerClassLoader customerClassLoader = new CustomerClassLoader(
Main.class.getClassLoader(),
"D:/CustomerClassLoaderRepo" );
Class<?> clazz = customerClassLoader.loadClass( "HelloWorld");
Object object = clazz.getConstructor(new Class[]{}).newInstance();
Method method = clazz.getMethod("sayHello", String.class);
method.invoke( object, "tom");
logClassLoaders(clazz);
}
public static <T> void logClassLoaders( Class<T> tClass) {
System.out.println( String.format(
"打印類 %s 的類加載器鏈:", tClass.getCanonicalName()));
ClassLoader classLoader = tClass.getClassLoader();
while ( classLoader != null) {
System.out.println( String.format(
"類加載器鏈:%s", classLoader.toString()));
classLoader = classLoader.getParent();
}
System.out.println( "類加載器鏈: Bootstrap class loader");
}
}
//D:/CustomerClassLoader.java
import java.io.IOException;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
public class CustomerClassLoader extends ClassLoader {
private String url_prefix;
public CustomerClassLoader(ClassLoader parent, String url_prefix) {
super(parent);
this.url_prefix = "file:///" + url_prefix;
}
@Override
protected Class<?> findClass(String name)
throws ClassNotFoundException {
byte[] bytes = loadClassData(url_prefix + "/" + name + ".class");
return super.defineClass(name, bytes, 0, bytes.length);
}
private byte[] loadClassData(String name)
throws ClassNotFoundException {
return loadClassDate(Paths.get(URI.create(name)));
}
private byte[] loadClassDate(Path path)
throws ClassNotFoundException {
if ( path!=null &&
path.isAbsolute() &&
Files.exists( path) &&
Files.isRegularFile( path)) {
try {
return Files.readAllBytes( path);
} catch ( IOException e) {
e.printStackTrace();
throw new ClassNotFoundException("文件讀取錯誤");
}
} else {
throw new ClassNotFoundException("文件地址不符合規(guī)則");
}
}
}
//D:/HelloWorld.java
public class HelloWorld {
public void sayHello( String name) {
System.out.println( "Hello World, I'm " + name);
}
}
3.2.2 文件位置
- D:
- \CustomerClassLoaderRepo
- HelloWorld.java
- HelloWorld.class
- Main.java
- Main.class
- CustomerClassLoader.java
- CustomerClassLoader.class
3. 運(yùn)行結(jié)果
4. 使用URLClassLoader
3.4.1 試驗代碼
以上只是筆者在學(xué)(發(fā))習(xí)(呆), 實際工作中我們可以使用諸多ClassLoader的子類來提升開發(fā)效率。如下是使用URLClassLoader來實現(xiàn)相同的功能。
//D:/Main.java
import java.lang.reflect.Method;
public class Main {
public static void main(String[] args) throws Exception {
//CustomerClassLoader customerClassLoader = new CustomerClassLoader(
// Main.class.getClassLoader(),
// "D:/CustomerClassLoaderRepo" );
//Class<?> clazz = customerClassLoader.loadClass( "HelloWorld");
URL url = new URL("file:///D:CustomerClassLoaderRepo/");
URLClassLoader urlClassLoader = new URLClassLoader(
new URL[]{url} , Main.class.getClassLoader());
Class<?> clazz = urlClassLoader.loadClass( "HelloWorld");
Object object = clazz.getConstructor(new Class[]{}).newInstance();
Method method = clazz.getMethod("sayHello", String.class);
method.invoke( object, "tom");
logClassLoaders(clazz);
urlClassLoader.close(); //釋放資源
}
public static <T> void logClassLoaders( Class<T> tClass) {
System.out.println( String.format(
"打印類 %s 的類加載器鏈:", tClass.getCanonicalName()));
ClassLoader classLoader = tClass.getClassLoader();
while ( classLoader != null) {
System.out.println( String.format(
"類加載器鏈:%s", classLoader.toString()));
classLoader = classLoader.getParent();
}
System.out.println( "類加載器鏈: Bootstrap class loader");
}
}
3.4.2 文件位置
- 同 3.3.2