應用服務器通常都支持熱部署(Hot Deployment),更新代碼時把新編譯的確類 替換舊的就行,後面的程序就執行新類中的代碼。這也是由各類應用服務器的獨 有的類加載器層次實現的。那如何在咱們的程序中也實現這種熱加載功能呢?即 要在虛擬機不關閉的狀況下(好比一個),換個類,JVM 就知道加載這個新類,執 行新類中的邏輯呢?下面就簡單演示這樣一個熱加載的例子,首先大體瞭解一下 類加載器。java
標準 Java 啓動器的類加載器層次編程
1. 引導類加載器(bootstrap): 加載內核 API,如 rt.jar(java.lang、 java.io 等)bootstrap
2. 擴展類加載器(extension): 加載的默認擴展來自於 jre/lib/ext服務器
3. 系統類加載器(system): 類路徑上的類,如 com.unmi.*ide
說明:這只是標準 Java 啓動器運行程序時的類加載器層次,像應用服務器中 的類加載器一般會多一兩層,也是在這個基礎上的延伸。上面的類加載層次存在 自上而下的委託關係,委託加載不在這裏細講。工具
類加載器的規則有三測試
1. 一致性規則:類加載器不能屢次加載同一個類ui
2. 委託規則 :在加載一個類以前,類加載器總參考父類加載器spa
3. 可見性規則:類只能看到由其類加載器的委託加載的其餘類,委託是類的 加載器及其全部父類加載器的遞歸集。(這個規則可能不太好理解,要舉個例子就 很容易理解的,這裏也不細說).net
實際的例子演示熱加載
1. 創建工程,編寫代碼
前面鋪墊的應該夠厚了,開始用個例子來講明感覺類的熱加載(又名熱部署 Hot Deployment)。這個例子採用 Eclipse 來作,首先要創建兩個普通的 Java 工程,分別是 TestHotDeployInf 和 TestHotDeployImpl。讓 TestHotDeployImpl 依賴於 TestHotDeployInf 工程,即在 TestHotDeployImpl 的 Build Path 中,Projects 標籤頁裏把 TestHotDeployInf 工程選進來,由於 編譯 TestHotDeployImpl 中的類要用到 TestHotDeployInf 中的類。
而後在工程式 TestHotDeployInf 中新建一個接口(Cat.java) 和一個類 (Client.java),內容分別是:
Cat.java(Cat 接口類,也能夠用抽象類,用來引用需熱加載的實現類的實 例)
package com.unmi;
/**
* Cat 接口,要熱加載的類必定要有一個 接口或基類引用
* @author Unmi
*/
public interface Cat {
public void miaow();
}
package com.unmi;
/**
* Cat 接 口,要熱加載的類必定要有一個接口或基類引用
* @author Unmi
*/
public interface Cat {
public void miaow();
}
Client.java(測試熱加載的客戶端類)
package com.unmi;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLClassLoader;
/**
* 測試熱部 署 Hot Deployment 的客戶端類
* @author Unmi
*/
public class Client {
private static ClassLoader cl;
private static Class catClass;
/**
* @param args
*/
public static void main(String[] args) throws Exception{
BufferedReader br = new BufferedReader(new InputStreamReader (System.in));
Cat cat = createCat();
System.out.println("miaow, reload, or exit");
while(true) {
String cmd = br.readLine();
if (cmd.equalsIgnoreCase("exit")){
return;
} else if(cmd.equalsIgnoreCase("reload")){
reloadImpl();
cat = createCat();
System.out.println("CatImpl reloaded.");
} else if (cmd.equalsIgnoreCase("miaow")){
cat.miaow();
}
}
}
/**
* 使用加載 的類 Cat 類建立 Cat 實例
* @return Cat 實例
* @throws Exception
*/
public static synchronized Cat createCat() throws Exception{
if(catClass==null){
reloadImpl();
}
Cat newCat = (Cat) catClass.newInstance();
return newCat;
}
/**
* 用自定義的類加載器從新加載 ../TestHotDeployImpl/bin 目錄中 的 CatImpl 實現類
* 注意這裏的 ../TestHotDeployImpl/bin,方便 直接讀取 TestHotDeployImpl 下隨時
* 修改後編譯成的新的 com.unmi.CatImpl 類,避免了 class 文件編譯後拷貝到別處
* @throws Exception
*/
public static synchronized void reloadImpl() throws Exception{
URL[] externalURLs = new URL []{new URL("file:../TestHotDeployImpl/bin/")};
cl = new URLClassLoader(externalURLs);
catClass = cl.loadClass ("com.unmi.CatImpl");
}
}
package com.unmi;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLClassLoader;
/**
* 測試熱部署 Hot Deployment 的客戶端類
* @author Unmi
*/
public class Client {
private static ClassLoader cl;
private static Class catClass;
/**
* @param args
*/
public static void main (String[] args) throws Exception{
還要在 TestHotDeployImpl 中添加一個 Cat 的實現類 CatImpl
package com.unmi;
/**
* Cat 的實現類,觀察是否加載了最新代 碼,可經過改變 miaow() 方法的輸出
* @author Unmi
*/
public class CatImpl implements Cat {
@Override
public void miaow() {
System.out.println("I'm Hello Kity, I like play with you.");
//System.out.println("I'm Tom, Jerry always kids me.");
}
}
package com.unmi;
/**
* Cat 的實 現類,觀察是否加載了最新代碼,可經過改變 miaow() 方法的輸出
* @author Unmi
*/
public class CatImpl implements Cat {
@Override
public void miaow() {
System.out.println("I'm Hello Kity, I like play with you.");
//System.out.println("I'm Tom, Jerry always kids me.");
}
}
本文來自編程入門網:http://www.bianceng.cn/Programming/Java/201108/28916_3.htm
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
Cat cat = createCat();
System.out.println("miaow, reload, or exit");
while(true){
String cmd = br.readLine();
if (cmd.equalsIgnoreCase("exit")){
return;
} else if (cmd.equalsIgnoreCase("reload")){
reloadImpl();
cat = createCat();
System.out.println("CatImpl reloaded.");
} else if(cmd.equalsIgnoreCase("miaow")){
cat.miaow();
}
}
}
/**
* 使用加載的類 Cat 類建立 Cat 實例
* @return Cat 實例
* @throws Exception
*/
public static synchronized Cat createCat() throws Exception{
if(catClass==null){
reloadImpl();
}
Cat newCat = (Cat)catClass.newInstance();
return newCat;
}
/**
* 用自定義的類加載器從新加載 ../TestHotDeployImpl/bin 目錄中的 CatImpl 實現類
* 注意這裏的 ../TestHotDeployImpl/bin,方便直接讀取 TestHotDeployImpl 下隨時
* 修改後編譯成的新的 com.unmi.CatImpl 類,避免了 class 文件編譯後拷貝到別 處
* @throws Exception
*/
public static synchronized void reloadImpl() throws Exception{
URL[] externalURLs = new URL[]{new URL("file:../TestHotDeployImpl/bin/")};
cl = new URLClassLoader (externalURLs);
catClass = cl.loadClass("com.unmi.CatImpl");
}
}
2. 進行測試
運行 TestHotDeployInf 中的 Client 程序,按照下圖中的指令說明,可觀察 到熱加載的過程:
3. 幾個問題
1) 爲何要在單獨的工程裏放置 CatImpl 類(重要)
主要是爲了編譯成的 CatImpl 類對於 TestHotDeployInf 的系統加載類不可 見,就是不能放在 TestHotDeployInf 的程序的 classpath 中。
這個問題能夠說大,本應該提升一個層次來講明它。前面提過標準 Java 啓動 器加載器層次中有三個加載器,而在上面的 Client.java 中,咱們看到用了一個 自定義的 cl = new URLClassLoader(externalURLs) 類加載器來加載 com.unmi.CatImpl。也就是標準的類加載器又多了一層,這裏估且把它叫作應用 程序加載器(AppClassloader)。
根據委託規則,執行 Client 時,要加載 com.unmi.CatImpl 時會首先委託加 載 Client 類自己的系統加載器加載。若是編譯出的 CatImpl.class 放在 Cat.class 相同的位置,那麼就由系統加載器來加載 com.unmi.CatImpl,自定義 加載器 cl 是沒機會了。因此必須放在外面讓系統加載器看不到 com.unmi.CatImpl 類。
再依據一致性規則,若是系統加載器能加載了 com.unmi.CatImpl 類,之後你 怎麼修改 CatImpl 類,替換掉原來的類,內存中老是最早加載的那個 com.unmi.CatImpl 類版本。由於類只會加載一次。而用自定義的 cl 可不同了 ,每次執行 cl.loadClass("com.unmi.CatImpl") 時都是用的一個新的 ClassLoader 實例,因此不受一致性規則的約束,每次都會加載最新版本的 CatImpl 類。
2)關於類的卸載的問題
上一條講了加載 com.unmi.CatImpl 時,每次都 new 了一個新了 ClassLoader 實例,每次都加載最新的 CatImpl 類,那就引出了再也不使用的 ClassLoader 實例和早先舊版本的 CatImpl 類實例的回收問題。在多數 JVM 中 ,它們如同普通的 Java 對象同樣的處理,當它們無從觸及時被看成垃圾被收集 掉。也可能在某些 JVM 中這種狀況對 ClassLoader 和舊版本 Class 實例的回收 要特殊關照一下。
這裏的 Class 實例,就是對象調用 getClass() 獲得的實例,如 CatImpl.getClass()。類實例和類加載器是相關聯的,全部會出現這樣的問題, 相同類的靜態變量可能表現爲不一樣的值,由於它們多是由不一樣的類加載器加載 的。
對於 ClassLoader 確未細細深刻,其實要展開的話內容也很少,關鍵就知道 兩點(仍是回到了前面的兩點,等於什麼都沒說哦):
1)瞭解你的程序的類加載器層次,應該看看常見應用服務器(如 Tomcat) 的類 加載器層次
2) 理解類加載器的三個規則,着重理解委託機制
知道了類加載器層次,你就能夠進行一些定製。如能夠把一些包丟到 jre/lib/ext 中就能使用到;給 java 用參數 -Xbootclasspath 指定別的類或包 就能替換掉 Java 核心 API 了。
對於可見性規則能夠舉兩個例子:
1) 對於標準的類加載器層次,放在 jre/lib/ext 中的類(由擴展類加載器加 載)可讓放在 classpath 下的類(由系統類加載器加載) 訪問到,反過來就不行 了。
2) 應用服務器中不一樣的 Web 應用中類不能相互訪問,由於它們是由不一樣的類 加載器加載的,且是在並行結構中。而在企業應用程序中的 WAR 包使用到 EJB 包和其餘工具包,由於加載 WAR 包的類加載層是在加載 EJB 包和其餘工具包的 類加載器的下層。
本文來自編程入門網:http://www.bianceng.cn/Programming/Java/201108/28916_4.htm