為什麽要自定義ClassLoader進行類加載
ClassLoader:加載各種class文件到JVM中。ClassLoader是抽象類。
類的加載過程分為加載階段、連接階段、初始化。
- 加載階段:尋找class文件。
- 連接階段:驗證class文件的正確性;為類的靜態變量準備內存,並為其初始化默認值;把類中符號引用轉換為直接引用。
- 初始化階段:為累的靜態變量賦予正確的初始值,也就是在代碼的值。
JVM目前對類的初始化時一次lazy。所以何時初始化就會根據不同情況而不同,如果主動使用,則會進行類的初始化。
類的6種主動使用情況:
- 通過調用靜態變量會導致類的初始化。 但是若是靜態常量,則不會。
- 調用靜態方法,會導致類的初始化過程。
- 調用class.forName。
- 調用子類會導致父類初始化,但是如果只是通過子類調用父內的靜態方法後者變量,則不會導致子類初始化。
- 調用main方法的類也會被初始化。
- 調用 new關鍵字,必然會進行類的初始化,但是數組除外。
類的被動使用:除了主動的以外,都是被動的。
類的加載過程中階段,常量是在javac編譯階段就替換了值,所以不會再類的連接階段進行默認初始化(給相應類變量一個相關類型在沒有被設置時候的默認值)。
類的初始化階段中,賦值動作和靜態語句塊的執行代碼,這個是按照順序執行。虛擬機會保證這些動作會優先執行,因此父類的靜態變量總是能夠得到優先賦值。
JVM默認的加載類去尋找特定的幾個目錄下的類進行加載。比如當前路徑,classpath等。若想自定義路徑,或者在加載過程中,有些個性化的需求,則需要自定義加載類進行加載。
自定義加載類,還有一個好處,可以根據需求,進行單獨卸載和重新加載,這樣就可以進行無需停機的熱部署。
下面時自定義加載類的使用方法:
- 先編寫一個簡單的類,用於測試被自定義類加載。
‘‘‘
package classLoaderStudy;
public class ClassLoaderHello {
static
{
System.out.println("hello world");
}
public String welcome()
return "Hello world";
}
}
‘‘‘
- 編寫自定義加載類,
其中findClass是必須重寫的,功能就是讀入class文件位字節流,然後調用ClassLoader的自帶方法defineClass方法對類在方法區進行定義,這樣,才能使用這個自定義類。
‘‘‘
package classLoaderStudy;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
public class MyClassLoader extends ClassLoader{
private final static Path DEFAULT_CLASS_DIR = Paths.get("E:", "classloader1");
private final Path classDir;
public MyClassLoader()
{
super();
this.classDir = DEFAULT_CLASS_DIR;
}
public MyClassLoader(String classDir)
{
super();
this.classDir = Paths.get(classDir);
}
public MyClassLoader(String classDir,ClassLoader parent)
{
super(parent);
this.classDir = Paths.get(classDir);
}
private byte[] readClassBytes(String name)
throws ClassNotFoundException
{
String classPath = name.replace(".", "/");
Path classFullPath = classDir.resolve(Paths.get(classPath+".class"));
if(!classFullPath.toFile().exists())
{
throw new ClassNotFoundException("The Class "+name+" not found");
}
try(ByteArrayOutputStream baos = new ByteArrayOutputStream())
{
Files.copy(classFullPath, baos);
return baos.toByteArray();
}
catch(IOException e)
{
throw new ClassNotFoundException("Load the class "+ name +" occur error.",e);
}
}
@Override
/*
* (non-Javadoc)
* @see java.lang.ClassLoader#findClass(java.lang.String)
* 必須要重寫這個類
*/
protected Class<?> findClass(String name) throws ClassNotFoundException
{
byte[] classBytes = this.readClassBytes(name);
if(null == classBytes || classBytes.length == 0)
{
throw new ClassNotFoundException("can not load the class ");
}
return this.defineClass(name, classBytes, 0,classBytes.length);
}
@Override
public String toString()
{
return "My ClassLoader";
}
public static void main(String[] args) {
MyClassLoader classLoader = new MyClassLoader();
Class<?> aClass =null;
try {
aClass = classLoader.loadClass("classLoaderStudy.ClassLoaderHello");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
System.out.println(aClass.getClassLoader());
try {
Object helloWorld = aClass.newInstance();
System.out.println(helloWorld);
Method welcomeMethod = aClass.getMethod("welcome");
String result = (String) welcomeMethod.invoke(helloWorld);
System.out.println(result);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}catch (Exception e)
{
e.printStackTrace();
}
}
}
‘‘‘
- 測試
將classLoaderStudy\\ClassLoaderHello.class
文件拷貝到 E:\\classloader1
目錄下,
如E:\\classloader1\\classLoaderStudy\\ClassLoaderHello.class
並將工程中的 ClassLoaderHello.java
文件和ClassLoaderHello.class
文件刪除,從而確保不會加載工程中的這個類,以便測試自定義加載類。
在Eclipse中運行MyClassLoader
類,結果如下
‘‘‘
My ClassLoader
hello worldclassLoaderStudy.ClassLoaderHello@7d4991ad
br/>classLoaderStudy.ClassLoaderHello@7d4991ad
‘‘‘
為什麽要自定義ClassLoader進行類加載