顧名思義,類加載器(ClassLoader)用來加載 Java 類到 Java 虛擬機中。一般來說,Java 虛擬機使用 Java 類的方式如下:Java 源程序(.java 文件)在經過 Java 編譯器編譯之後就被轉換成 Java 字節代碼(.class 文件)。類加載器負責讀取 Java 字節代碼,並轉換成 java.lang.Class類的一個實例。每個這樣的實例用來表示一個 Java 類。通過此實例的 newInstance()方法就可以創建出該類的一個對象。實際的情況可能更加複雜,比如 Java 字節代碼可能是通過工具動態生成的,也可能是通過網絡下載的。
ClassLoader是負責加載類的對象。ClassLoader是一個抽象類。給定類的二進制名稱,ClassLoader應嘗試查找或生成構成類定義的數據。典型的策略是將名稱轉換爲文件名,然後從文件系統中讀取該名稱的“類文件”。
每個Class類對象都包含一個Class.getClassLoader()引用,指向定義它的ClassLoader。
數組類的對象不是由ClassLoader創建的,而是根據Java運行時的要求自動創建的。由Class.getClassLoader()返回的數組類的ClassLoader與元素類型的ClassLoader相同;如果元素類型是基元類型,則數組類沒有ClassLoader。
應用程序實現ClassLoader的子類,以擴展Java虛擬機動態加載類的方式。ClassLoader通常可由安全管理器用於指定安全域。
ClassLoader類使用委託模型來搜索類和資源。ClassLoader的每個實例都有一個相關的父ClassLoader。當請求查找類或資源時,ClassLoader實例將在試圖查找類或資源本身之前,將對該類或資源的搜索委託給其父ClassLoader。虛擬機的內置ClassLoader稱爲“引導類加載器”,它本身沒有父類,但可以充當ClassLoader實例的父類。
支持類的併發加載的ClassLoader稱爲支持並行的ClassLoader,需要在類初始化時通過調用{ClassLoader.registeraspallelcapable}方法註冊它們自己。注意,默認情況下,ClassLoader類註冊爲支持並行。但是,如果其子類具有並行能力,則仍需要註冊它們自己。在委託模型不是嚴格分層的環境中,ClassLoader需要具有並行能力,否則類裝入可能導致死鎖,因爲裝入器鎖在類裝入過程的持續時間內保持。
通常,Java虛擬機以依賴於平臺的方式從本地文件系統加載類。例如,在UNIX系統上,虛擬機從由CLASSPATH環境變量定義的目錄加載類。
但是,有些類可能不是源於文件;它們可能源於其他源,例如網絡,或者可以由應用程序構造。方法{defineClass(String,byte[],int,int)}將字節數組轉換爲類的實例。這個新定義的類的實例可以使用{class.newInstance}創建。
Java 中的類加載器大致可以分成兩類,一類是系統提供的,另外一類則是由 Java 應用開發人員編寫的。系統提供的類加載器主要有下面三個:
-
引導類加載器(bootstrap class loader):它用來加載 Java 的核心庫,是用原生代碼來實現的,並不繼承自
java.lang.ClassLoader
。 -
擴展類加載器(extensions class loader):它用來加載 Java 的擴展庫。Java 虛擬機的實現會提供一個擴展庫目錄。該類加載器在此目錄裏面查找並加載 Java 類。
-
系統類加載器(system class loader):它根據 Java 應用的類路徑(CLASSPATH)來加載 Java 類。一般來說,Java 應用的類都是由它來完成加載的。可以通過
ClassLoader.getSystemClassLoader()
來獲取它。
開發人員可以通過繼承 java.lang.ClassLoader
類的方式實現自己的類加載器,以滿足一些特殊的需求。
除了引導類加載器之外,所有的類加載器都有一個父類加載器。通過 getParent()
方法可以得到。
public static void main(String[] args) {
ClassLoader classLoader = ClassLoaderDemo.class.getClassLoader();
if (classLoader != null){
System.out.println(classLoader.getParent().toString());
//輸出:sun.misc.Launcher$ExtClassLoader@379619aa
}
}
ClassLoader中方法很多,但是與加載類相關的方法有下面這個幾個:
getParent()
@CallerSensitive
public final ClassLoader getParent() {
if (parent == null)
return null;
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
// 權限檢查
checkClassLoaderPermission(parent, Reflection.getCallerClass());
}
return parent;
}
loadClass(String name)
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 首先,檢查類是否已加載
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
//返回引導類加載器加載的類;如果找不到,則返回null
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// 如果仍然找不到,則調用findClass找到該類
long t1 = System.nanoTime();
//findClass需要重寫
c = findClass(name);
//這是定義類裝入器;記錄統計信息
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
//鏈接指定的 Java 類
resolveClass(c);
}
return c;
}
}
findClass(String name)
這個方法需要子類進行重寫。
findLoadedClass(String name)
protected final Class<?> findLoadedClass(String name) {
if (!checkName(name))
return null;
return findLoadedClass0(name);
}
private native final Class<?> findLoadedClass0(String name);
defineClass(String name, byte[] b, int off, int len)
protected final Class<?> defineClass(String name, byte[] b, int off, int len)
throws ClassFormatError
{
return defineClass(name, b, off, len, null);
}
protected final Class<?> defineClass(String name, byte[] b, int off, int len,
ProtectionDomain protectionDomain)
throws ClassFormatError
{
//確定保護域
protectionDomain = preDefineClass(name, protectionDomain);
String source = defineClassSourceLocation(protectionDomain);
//定義類
Class<?> c = defineClass1(name, b, off, len, protectionDomain, source);
//設置證書
postDefineClass(c, protectionDomain);
return c;
}
開發自己的類加載器
public class CustomClassLoader extends ClassLoader {
private String rootDir;
public CustomClassLoader(String rootDir) {
this.rootDir = rootDir;
}
//重寫findClass
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);
//獲取文件中的類內容
//獲取文件中的類內容
return null;
}
private String classNameToPath(String className) {
return rootDir + File.separatorChar
+ className.replace('.', File.separatorChar) + ".class";
}
}
類加載器與web容器
對於運行在 Java EE™容器中的 Web 應用來說,類加載器的實現方式與一般的 Java 應用有所不同。不同的 Web 容器的實現方式也會有所不同。以 Tomcat 來說,每個 Web 應用都有一個對應的類加載器實例。該類加載器也使用代理模式,所不同的是它是首先嚐試去加載某個類,如果找不到再代理給父類加載器。這與一般類加載器的順序是相反的。這是 Java Servlet 規範中的推薦做法,其目的是使得 Web 應用自己的類的優先級高於 Web 容器提供的類。這種代理模式的一個例外是:Java 核心庫的類是不在查找範圍之內的。這也是爲了保證 Java 核心庫的類型安全。
絕大多數情況下,Web 應用的開發人員不需要考慮與類加載器相關的細節。下面給出幾條簡單的原則:
-
每個 Web 應用自己的 Java 類文件和使用的庫的 jar 包,分別放在
WEB-INF/classes
和WEB-INF/lib
目錄下面。 -
多個應用共享的 Java 類文件和 jar 包,分別放在 Web 容器指定的由所有 Web 應用共享的目錄下面。
-
當出現找不到類的錯誤時,檢查當前類的類加載器和當前線程的上下文類加載器是否正確。
更多精彩內容請關注微信公衆號: