Java 動態加載類

 

第一部分:Java虛擬機啓動時,關於類加載方面的一些動作

 

當使用java ProgramName.class運行程序時,Java找到JRE,接着找到jvm.dll,把該動態庫載入內存,這就是JVM。然後加載其它動態庫, 並激活JVM。JVM激活之後會進行一些初始化工作,之後生成BootstrapLoader,該Class Loader是由C++寫的。BootstrapLoader加載Launcher.java中的ExtClassLoader,並設定其Parent爲 null,意思是ExtClassLoader的Parent Class Loader就是BootstrapLoader。然後BootstrapLoader又加載Launcher.java中的 AppClassLoade,並設定其Parent Class Loader是ExtClassLoader。不過如果調用ExtClassLoader的getParent( )方法,則返回的是null。這兩個Class Loader都是以靜態類的形式存在,並且都是用Java編寫的。
這三個Class Loader它們都有自己的類查找路徑:
BootstrapLoader: sun.boot.class.path
ExtClassLoader: java.ext.dirs
AppClassLoader: java.class.path
以上三個路徑都是Java的系統屬性,可以通過System.getProperty(String key)方法來查看其設置:
System.out.println(System.getProperty("sun.boot.class.path"));
輸出:
C:/Program Files/Java/jre1.5.0_04/lib/rt.jar;
C:/Program Files/Java/jre1.5.0_04/lib/i18n.jar;
C:/Program Files/Java/jre1.5.0_04/lib/sunrsasign.jar;
C:/Program Files/Java/jre1.5.0_04/lib/jsse.jar;
C:/Program Files/Java/jre1.5.0_04/lib/jce.jar;
C:/Program Files/Java/jre1.5.0_04/lib/charsets.jar;
C:/Program Files/Java/jre1.5.0_04/classes
即BootstrapLoader 加載的是jre和jre/lib目錄下的class
System.out.println(System.getProperty("java.class.path"));
輸出:
C:/Program Files/Java/jre1.5.0_04/lib/ext
即ExtClassLoader加載的是jre/lib/ext下的class
System.out.println(System.getProperty("java.class.path"));
輸出:應用裏的路徑
由此可見,BootstrapLoader負責Java核心類(所有以java.*開頭的類)。ExtClassLoader負責加載擴展類(所以以javax.*開頭的類以及存在ext目錄下的類)。AppClassLoader負責加載應用程序自身的類。
第二部分:Java的類加載機制。Java是如何加載類的,其流程。
類加載按照加載時機,是否自動加載分爲兩種:預先加載和按需加載。
預先加載的類是在JVM啓動之後,應用程序運行之前。至少包含rt.jar中的所有類。
按需加載則是在應用程序運行之後,在程序運行過程中,JVM遇到一個還未被裝載的類,這時由Class Loader把該類載入內存。
 
類加載按照方式來分,也是兩種:隱式加載和顯式加載。
隱式加載是通過new的方式,在類初始化時由JVM根據相應的Class Loader將類載入。
顯式加載則是程序員在代碼中顯式利用某個Class Loader將類載入。
 
JVM自動裝載類的算法是這樣的:如果Class A的實例引用了Class B的實例,則在默認情況下,JVM會先找到Class A的Class Loader,然後用該Class Loader來裝載Class B。
 
Class Loader裝載類的一般算法如下:
Background: Class Loader是按照層次關係組織起來的,每一個Class Loader都有一個Parent。如果在創建Class Loader時不顯式指定其父Class Loader,JVM會把系統Class Loader指定爲該Class Loader的Parent。每一個Class Loader都有自己對應的Loaded Class Cache,換句話說,Loaded Class Cache由兩部分組成:ClassLoader,以及由它加載的Class類名。
  1. 檢查這個類是否已經被加載進去了
  2. 如果還沒有加載,調用父對象加載該類
  3. 如果父對象無法加載,調用本對象的findClass()取得這個類。
所以當創建自己的Class Loader時,只需要重載findClass()方法。
Java 1.2之後,類的裝載採用委託模式。
一個已經加載的類是無法被更新的,如果試圖用同一個ClassLoader再次加載 同一個類,會得到異常java.lang.LinkageError: duplicate class definition。只能夠重新創建一個新的ClassLoader實例來再次加載新類。
 
第三部分:定義自己的Class Loader
爲什麼要使用自己的ClassLoader?
因爲JVM自帶的ClassLoader只是懂得從本地文件系統加載標準的java class文件,如果編寫自己的ClassLoader,可以
  1. 在執行非置信代碼之前,自動驗證數字簽名
  2. 動態地創建符合用戶特定需要的定製化構建類
  3. 從特定的場所取得java class,例如數據庫和網絡。

當創建自己的ClassLoader時,需要繼承java.lang.ClassLoader或者它的子類。在實例化每個ClassLoader對 象時,需要指定一個父對象;如果沒有的話,系統自動指定ClassLoader.getSystemClassLoader()爲父對象。

第四部分:程序中顯示加載並實例化類的幾種方式:
1) 使用Class類
Class foo = Class.forName(String ClassTypeName);  // 通過調用ClassLoa der.getCallerClassLoader( )得到當前Class Loader,然後查找並載入ClassTypeName。
or
Class foo = Class.forName(String ClassTypeName, boolean initialize, ClassLoader loader)  // 顯式指定用哪個Class Loader來查找並載入ClassTypeName。
 
ClassTypeName boo = (ClassTypeName) foo.newInstance( );
 
2) 通過ClassLoader的子類針對不同情況裝載類,比如java.net.URLClassLoader等。
 
獲取當前ClassLoader的方法:
ClassLoader foo = Thread.currentTread().getCoontextClassLoader();

第五部分: Class.forName()與ClassLoader.loadClass()的區別

Class clazz = Class.forName("XXX.XXX");

ClassLoader cl = Thread.currentThread().getContextClassLoader();
Class clazz = cl.loadClass("XXX.XXX");
都可以裝載一個類那麼他們的區別是什麼呢?
進一步研究Class.forName()是調用
Class.forName(name, initialize, loader); 也就是Class.forName("XXX.XXX"); 等同與 Class.forName("XXX.XXX", true, CALLCLASS.class.getClassLoader());

第二次參數表示裝載類的時候是否初始化該類, 即調用類的靜態塊的語句及初始化靜態成員變量。

Class clazz = cl.loadClass("XXX.XXX"); 沒有指定是否初始化的選項。只有執行clazz.newInstance();時才能夠初始化類。可以說 Class.forName("XXX.XXX", false, cl)執行過程是一致的。只是ClassLoader.loadClass()是更底 層的操作。

看一下JDBC驅動的裝載。
Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriverManager.getConnection("jdbcurl");
當調用Class.forName("com.mysql.jdbc.Driver");是Driver已經被初始化並註冊到DriverManager中。MySQL Driver的代碼
public class Driver extends NonRegisteringDriver
implements java.sql.Driver
{

public Driver()
throws SQLException
{
}

static 
{
try
{
DriverManager.registerDriver(new Driver());
}
catch(SQLException E)
{
throw new RuntimeException("Can't register driver!");
}
}
}
改修JDBC驅動的裝載
ClassLoader cl = Thread.currentThread().getContextClassLoader();
Class clazz = cl.loadClass("com.mysql.jdbc.Driver");
clazz.newInstance();
Connection conn = DriverManager.getConnection("jdbcurl");
同樣可以執行。

進一步說:

Class.forName是從指定的classloader中裝載類,如果沒有指定,也就是一個參數的時候,是從裝載當前對象實例所在的classloader中裝載類. 
而ClassLoader的實例調用loadclass方法,是指從當前ClassLoader實例中調用類,而這個實例與裝載當前所在類實例的Classloader也許不是同一個
舉個例子吧, 有A,B , C兩個ClassLoader , 當前運行的類D的實例是d(裝載它的是A) ,  如果D中使用Class.forName那麼就是使用的ClassLoader就是A,當然,也可以指定爲B. 而如果D中代碼找到的ClassLoader實例是C,那麼就是用D來裝載所指定的類. 

爲什麼要用不同的ClassLoader 裝載?
舉例來說:如果在Class被載入的過程中,你希望使用在自己的Class Loader來實現特定的操作,請使用ClassLoader方式。 

貌似CGLib之類的bytecode generation框架很多地方會使用指定特殊ClassLoader的方式。
使用多個classloader的情況非常常見,比如說我們的app server,那麼都是這樣的. 在Web與EJB間, 他們的classLoader就是不同的,這樣做的目的就是爲了避免兩者間類裝載的相互干擾.

再舉個例子:
Static初始化區塊在什麼時候被調用的問題?
Public A{Static{System.out.println(“HaHaHa”);}}
Class.forName(“A”);
Class.forName(“A”,false, ClassLoader.getSystemClassLoader());
看看奧妙在哪裏?Java ClassLoader機制和原理又是如何?

程序示例:
public class A {
static { System.out.println("A`static is executed!");}
public A() {System.out.println("A`construct is executed!");}
public void show(){System.out.println("A`method is executed!");}
}

調用程序1:
Class c = Class.forName("A");
Method m = c.getMethod("show", new Class[0]);
System.out.println("A`test is executed!");
Object obj = c.newInstance();
m.invoke(obj, new Object[0]);

執行結果:
A`static is executed!
A`test is executed!
A`construct is executed!
A`method is executed!
調用程序2:
Class c = ClassLoader.getSystemClassLoader().loadClass("A");
System.out.println("A`test is executed!");
Method m = c.getMethod("show", new Class[0]);
Object obj = c.newInstance();
m.invoke(obj, new Object[0]);

執行結果:
A`test is executed!
A`static is executed!
A`construct is executed!
A`method is executed!
可見執行順序爲先執行 static{}塊中的代碼,然後執行構造函數,之後纔是方法的調用。
classloader的兩種載入方式:
1)pre-loading預先載入,載入基礎類 
2)load-on-demand按需求載入 
java動態載入class的兩種方式: 
1)implic it隱式,即利用實例化才載入的特性來動態載入class 
2)explic it顯式方式,又分兩種方式: 
a)java.lang.Class的forName()方法  (上述調用程序1採用此方式載入)
b)java.lang.ClassLoader的loadClass()方法(上述調用程序2採用此方式載入)
static塊在什麼時候執行? 
當調用forName(String)載入class時執行,(這個過程在類的所有父類中遞歸地調用)
如果調用ClassLoader.loadClass並不會執行.
forName(String,false,ClassLoader)時也不會執行. 
如果載入Class時沒有執行static塊則在第一次實例化時執行.比如new ,Class.newInstance()操作 
static塊僅執行一次

發佈了32 篇原創文章 · 獲贊 4 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章