熱部署初探與雙親委派機制
一、熱部署初探
? 熱部署就是在不重啟服務的情況下,無需重新啟動整個應用,就能對代碼、配置等進行更新并使新的更改在服務中生效。以下代碼可以打破雙親委派機制,利用類加載器的隔離實現熱部署。可分為以下三步進行:
-
自定義類加載器
public class HotClassLoader extends ClassLoader {// 1. 指定父類加載器(默認系統類加載器)public HotClassLoader(ClassLoader parent) {super(parent);}// 2. 核心方法:從字節碼加載類public Class<?> loadByte(byte[] classByte) {return defineClass(null, classByte, 0, classByte.length);} }
-
加載類定義
public class HotDemo {public void printVersion() {System.out.println("【原始版本】9.0");}public static void main(String[] args) throws Exception {} }
-
測試類
public class HotLoadTest {public static void main(String[] args) throws Exception {// 注意: 這是一個循環。while (true) {// 1. 讀取更新后的class文件byte[] bytes = Files.readAllBytes(Paths.get("E:\\IDEAProjects\\JavaBasicKnowladge\\out\\production\\JavaBasicKnowladge\\orverLoad\\HotDemo.class"));// 2. 用自定義加載器加載HotClassLoader loader = new HotClassLoader(HotLoadTest.class.getClassLoader());Class<?> clazz = loader.loadByte(bytes);// 3. 反射調用方法Object obj = clazz.getDeclaredConstructor().newInstance();clazz.getMethod("printVersion").invoke(obj);Thread.sleep(3000); // 3秒后重新加載}} }
熱部署機制:
- 打破類加載緩存:通常來說,類加載器會對已加載的類進行緩存,減少重復創建。上面程序每次新建HotClassLoader實例,利用不同類加載器的獨立命名空間,即使類名相同。JVM也會將其視為不同的類,從而繞過緩存機制。(類加載器的命名空間(Namespace) 是JVM用于隔離不同類加載器加載的類的核心機制。JVM判斷類的唯一性,通過類的全限定名與加載該類的加載器實例判斷。)
- 動態加載字節碼:loadByte()方法直接調用defineClass(),這一步繞過了傳統的類加載流程,將外部傳入的最新字節碼動態轉換為Class對象。這使得修改后的類無需重啟即可被加載。
- 隔離父類加載器:顯式指定父加載器,并確保目標類未被父加載器加載過,避免雙親委派機制導致無法重新加載。
二、打破雙親委派機制
-
打破雙親委派機制類加載器
public class BreakDelegateLoader extends ClassLoader {// 必須重寫loadClass而非findClass@Overridepublic Class<?> loadClass(String name) throws ClassNotFoundException {// 1. 優先檢查是否已加載Class<?> c = findLoadedClass(name);if (c != null) {return c;}// 2. 針對特定包名打破委派if (name.startsWith("com.example.breaking")) {return findClass(name);}// 3. 其他類仍走雙親委派return super.loadClass(name);}@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {try {byte[] data = loadClassData(name);return defineClass(name, data, 0, data.length);} catch (IOException e) {throw new ClassNotFoundException(name);}}private byte[] loadClassData(String className) throws IOException {String path = className.replace('.', '/') + ".class";try (InputStream ins = getClass().getClassLoader().getResourceAsStream(path)) {// Java 8 兼容寫法ByteArrayOutputStream buffer = new ByteArrayOutputStream();byte[] data = new byte[1024];int bytesRead;while ((bytesRead = ins.read(data, 0, data.length)) != -1) {buffer.write(data, 0, bytesRead);}return buffer.toByteArray();}} }
-
加載類
public class TestClass {static {System.out.println("【類加載】初始化完成,加載器:" +TestClass.class.getClassLoader());}// 添加構造方法調用public TestClass() {System.out.println("實例已創建");} }
-
測試類
public class BB {public static void main(String[] args) throws Exception {// 使用自定義加載器優先加載BreakDelegateLoader loader = new BreakDelegateLoader();// 必須通過自定義加載器觸發首次加載Class<?> c1 = loader.loadClass("com.example.breaking.TestClass");c1.newInstance(); // 觸發初始化// 再用系統加載器加載Class<?> c2 = Class.forName("com.example.breaking.TestClass");c2.newInstance();System.out.println("是否為同一個類: " + (c1 == c2)); // 應為false} }
示意圖:
機制 | 優勢 | 典型場景 |
---|---|---|
雙親委派 | 安全性、類唯一性、避免沖突 | 常規Java應用、核心類庫加載 |
打破雙親委派 | 靈活性、隔離性、動態性 | Web容器、熱部署、SPI、插件化架構 |
核心權衡:在安全穩定與靈活擴展之間取舍。雙親委派是默認的“安全模式”,而打破它是為了滿足特定場景下的高級需求。理解兩者的優劣,才能合理設計類加載策略。