應用服務器(如 Tomcat、JBoss)普遍支援熱部署:更新代碼後,只需替換編譯好的 .class 檔案,後續請求即會執行新邏輯。這得益於應用服務器獨特的類加載器層次結構。那麼,在普通 Java 程式中,我們能否在不重啟 JVM 的情況下,動態替換類並執行新版本邏輯?本文將通過一個完整範例,演示如何利用自定義 ClassLoader 實現類的熱加載。
標準 Java 啟動器採用雙親委派模型,主要包含三層:
rt.jar 中的 java.lang、java.io 等)。jre/lib/ext 目錄下的擴展類。com.unmi.*)。應用服務器在此基礎上再增加一兩層,但核心原理相同:存在自上而下的委派關係。
在 Eclipse 中建立兩個普通 Java 專案:
將 TestHotDeployInf 加入 TestHotDeployImpl 的 Build Path 中。
package com.unmi;
/** 熱加載的類必須有一個介面或基類引用 */
public interface Cat {
public void miaow();
}
package com.unmi;
import java.io.*;
import java.net.*;
public class Client {
private static ClassLoader cl;
private static Class catClass;
public static void main(String[] args) throws Exception {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
Cat cat = createCat();
System.out.println("輸入指令: miaow, reload, 或 exit");
while (true) {
String cmd = br.readLine();
if (cmd.equalsIgnoreCase("exit")) return;
else if (cmd.equalsIgnoreCase("reload")) {
reloadImpl();
cat = createCat();
System.out.println("CatImpl 已重新加載。");
} else if (cmd.equalsIgnoreCase("miaow")) {
cat.miaow();
}
}
}
/** 使用已加載的 Cat 類創建實例 */
public static synchronized Cat createCat() throws Exception {
if (catClass == null) reloadImpl();
return (Cat) catClass.newInstance();
}
/** 使用自定義 ClassLoader 重新加載 ../TestHotDeployImpl/bin 下的 CatImpl */
public static synchronized void reloadImpl() throws Exception {
URL[] urls = new URL[]{new URL("file:../TestHotDeployImpl/bin/")};
cl = new URLClassLoader(urls);
catClass = cl.loadClass("com.unmi.CatImpl");
}
}
package com.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.");
}
}
TestHotDeployInf 中的 Client.main。miaow,觀察輸出。CatImpl.miaow() 的輸出字串,保存(Eclipse 自動編譯)。reload,再次輸入 miaow,發現輸出已更新。
若 CatImpl.class 與 Cat.class 在同一 Classpath,系統類加載器會優先加載它(委派規則),導致自定義 URLClassLoader 無機會介入。必須讓系統類加載器「看不見」CatImpl,才能由子加載器接管。
此外,一致性規則規定同一加載器只能加載一次。每次 new URLClassLoader(...) 創建新實例,便繞過此限制,實現每次加載最新版本。
每次重新加載都會產生新的 ClassLoader 與 Class 實例。舊實例在無引用時會被 GC 回收,但在某些 JVM 中需特別注意。相同類由不同加載器加載,其靜態變數互不相干,這點在設計狀態保持時必須考慮。
-Xbootclasspath 或 jre/lib/ext 可替換核心 API,但生產環境慎用。本範例展示了 Java 類熱加載的核心機制,為理解框架熱部署、插件系統奠定基礎。