程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 類的熱加載(Hot Deployment)的簡單例子

類的熱加載(Hot Deployment)的簡單例子

編輯:關於JAVA

應用服務器一般都支持熱部署(Hot Deployment),更新代碼時把新編譯的確類 替換舊的就行,後面的程序就執行新類中的代碼。這也是由各種應用服務器的獨 有的類加載器層次實現的。那如何在我們的程序中也實現這種熱加載功能呢?即 要在虛擬機不關閉的情況下(比如一個),換個類,JVM 就知道加載這個新類,執 行新類中的邏輯呢?下面就簡單演示這樣一個熱加載的例子,首先大致了解一下 類加載器。

標准 Java 啟動器的類加載器層次

1. 引導類加載器(bootstrap):  加載內核 API,如 rt.jar(java.lang、 java.io 等)

2. 擴展類加載器(extension):  加載的默認擴展來自於 jre/lib/ext

3. 系統類加載器(system):    類路徑上的類,如 com.unmi.*

說明:這只是標准 Java 啟動器運行程序時的類加載器層次,像應用服務器中 的類加載器通常會多一兩層,也是在這個基礎上的延伸。上面的類加載層次存在 自上而下的委托關系,委托加載不在這裡細講。

類加載器的規則有三

1. 一致性規則:類加載器不能多次加載同一個類

2. 委托規則 :在加載一個類之前,類加載器總參考父類加載器

3. 可見性規則:類只能看到由其類加載器的委托加載的其他類,委托是類的 加載器及其所有父類加載器的遞歸集。(這個規則可能不太好理解,要舉個例子就 很容易理解的,這裡也不細說)

實際的例子演示熱加載

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{
 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");
}
}

還要在 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.");
}
}

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 包和其他工具包的 類加載器的下層。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved