類加載器基本概念
顧名思義,類加載器(class loader)用來加載 Java 類到 Java 虛擬機中。一般來說,Java 虛擬機使用 Java 類的方式如下:Java 源程序(.java 文件)在經過 Java 編譯器編譯之后就被轉換成 Java 字節代碼(.class 文件)。類加載器負責讀取 Java 字節代碼,并轉換成 java.lang.Class
類的一個實例。每個這樣的實例用來表示一個 Java 類。通過此實例的 newInstance()
方法就可以創建出該類的一個對象。實際的情況可能更加復雜,比如 Java 字節代碼可能是通過工具動態生成的,也可能是通過網絡下載的。
基本上所有的類加載器都是 java.lang.ClassLoader
類的一個實例。
public abstract class ClassLoader
extends Object
類加載器的樹狀組織結構
Java 中的類加載器大致可以分成兩類,一類是系統提供的,另外一類則是由 Java 應用開發人員編寫的。系統提供的類加載器主要有下面三個:
- 引導類加載器(bootstrap class loader):它用來加載 Java 的核心庫,是用原生代碼來實現的,并不繼承自
java.lang.ClassLoader
。其父類加載器為NULL。 - 擴展類加載器(extensions class loader):它用來加載 Java 的擴展庫。Java 虛擬機的實現會提供一個擴展庫目錄。該類加載器在此目錄里面查找并加載 Java 類。
- 系統類加載器(system class loader):它根據 Java 應用的類路徑(CLASSPATH)來加載 Java 類。一般來說,Java 應用的類都是由它來完成加載的。可以通過
ClassLoader.getSystemClassLoader()
來獲取它。
除了系統提供的類加載器以外,開發人員可以通過繼承 java.lang.ClassLoader
類的方式實現自己的類加載器,以滿足一些特殊的需求。
除了引導類加載器之外,所有的類加載器都有一個父類加載器。通過ClassLoader中給出的 getParent()
方法可以得到。對于系統提供的類加載器來說,系統類加載器的父類加載器是擴展類加載器,而擴展類加載器的父類加載器是引導類加載器;對于開發人員編寫的類加載器來說,其父類加載器是加載此類加載器 Java 類的類加載器。因為類加載器 Java 類如同其它的 Java 類一樣,也是要由類加載器來加載的。一般來說,開發人員編寫的類加載器的父類加載器是系統類加載器。類加載器通過這種方式組織起來,形成樹狀結構。樹的根節點就是引導類加載器。圖 1中給出了一個典型的類加載器樹狀組織結構示意圖,其中的箭頭指向的是父類加載器。
圖 1. 類加載器樹狀組織結構示意圖
類是由它的全名和類加載器來確定的,由此我們可以看到一個有趣的現象即
在同一個虛擬機中,可以有兩個類,它們的類名和包名都是相同的。這項技術在加載來自多處的代碼時非常有用如熱布署就是應用這一特性來實現的。
JVM啟動時,首先加載類加載器及其相對應的字節碼(class),其順序為:
先是bootstrap classloader------(rt.jar),然后是extension classloader
------(jre/lib/ext) ,最后才是system classloader
------(CLASSPATH) 。大家會發現加載的Class越是重要的越在靠前面。這樣做的原因是出于安全性的考慮,試想如果system classloader“親自”加載了一個具有破壞性的“java.lang.System”類的后果吧。這種委托機制保證了用戶即使具有一個這樣的類,也把它加入到了類路徑中,但是它永遠不會被載入,因為這個類總是由bootstrap classloader來加載的。大家可以執行一下以下的代碼:
System.out.println(System.class.getClassLoader());
將會看到結果是null,這就表明java.lang.System是由bootstrap classloader加載的,因為bootstrap classloader(用C++實現的)不是一個真正的ClassLoader實例,而是由JVM實現的。
類的加載過程:
我們知道每個類都有它自已的類加載器(Class.getClassLoader), 除了引導類加載器之外,所有的類加載器都有一個父類加載器。通過ClassLoader中給出的 getParent()
方法可以得到。對于系統提供的類加載器來說,系統類加載器的父類加載器是擴展類加載器,而擴展類加載器的父類加載器是引導類加載器;對于開發人員編寫的類加載器來說,其父類加載器是加載此類加載器 Java 類的類加載器。
編寫你自已的類加載器:
只需要繼承ClassLoader類,然后重寫其findClass(String classname)即可。
ClassLoader超類的loadClass方法用于將類的加載操作委托給其
父類加載器去進行,只有當類尚未加載并且其所有父類加載器也沒有加載此類時,才能調用你自已的類加載器中的findclass方法。
例法:
class CryptoClassLoader extends ClassLoader
{
/**
* Constructs a crypto class loader.
* @param k the decryption key
*/
public CryptoClassLoader(int k)
{
key = k;
}
protected Class findClass(String name)
{
byte[] classBytes = null;
try {
classBytes = loadClassBytes(name);
} catch (IOException e) {
e.printStackTrace();
}
Class cl = defineClass(name, classBytes, 0, classBytes.length);
if (cl == null)
try {
throw new ClassNotFoundException(name);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return cl;
}
/**
* Loads and decrypt the class file bytes.
* @param name the class name
* @return an array with the class file bytes
*/
private byte[] loadClassBytes(String name) throws IOException
{
// String cname="classes/com/jimmy/corejava/classloaderTest"+"/"+name+".class";
String cname="classes/com/jimmy/corejava/classloaderTest"+"/"+name+".class";
FileInputStream in = null;
in = new FileInputStream(cname);
try
{
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
int ch;
while ((ch = in.read()) != -1)
{
buffer.write(ch);
}
in.close();
return buffer.toByteArray();
}
finally
{
in.close();
}
}
private int key;
}
為方便測試時,我們可事先給無法找到類的路徑,以防止其其父類加載器加載其類,這們強迫它調用我們的類加載器,然后在我們的findclass、loadClassBytes 方法中給出能找到此類的路徑,這樣就能得到我們的目的了。
public class ClassLoaderTest
{
public static void main(String[] args)
{
// String firstClass="v2ch09.ClassLoaderTest.Calculator";
String firstClass="FirstClass";
try {
ClassLoader loader = new CryptoClassLoader(Integer.parseInt("3"));
System.out.println("=====>"+ClassLoaderTest.class.getClassLoader().toString());
Object fl= loader.loadClass(firstClass).newInstance();
System.out.println("=====>"+fl.getClass().getClassLoader().toString());
System.out.println(System.class.getClassLoader());
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
打印結果為:
=====>sun.misc.Launcher$AppClassLoader@addbf1
=====>v2ch09.ClassLoaderTest.CryptoClassLoader@61de33
運用自定義的類加載器,我們可以實現代碼的加密、解密等。
posted on 2012-05-30 22:35
jimmy2009 閱讀(370)
評論(0) 編輯 收藏