JAVA基礎(三)ClassLoader實現熱加載

Java極客  |  做者  /  鏗然一葉
這是Java極客的第 63 篇原創文章

相關閱讀:java

JAVA基礎(一)簡單、透徹理解內部類和靜態內部類
JAVA基礎(二)內存優化-使用Java引用作緩存
JAVA基礎(四)枚舉(enum)和常量定義,工廠類使用對比
JAVA基礎(五)函數式接口-複用,解耦之利刃
JAVA編程思想(一)經過依賴注入增長擴展性
JAVA編程思想(二)如何面向接口編程
JAVA編程思想(三)去掉彆扭的if,自注冊策略模式優雅知足開閉原則
JAVA編程思想(四)Builder模式經典範式以及和工廠模式如何選?
HikariPool源碼(二)設計思想借鑑
人在職場(一)IT大廠生存法則android


1. 應用場景

  1. 修復bug,不須要重啓服務,動態加載修改的bug類。
  2. 動態升級,在android系統中,能夠經過動態加載APK繞過應用市場的的升級策略,自行定製升級策略。

2. 例子

網上描述ClassLoader加載的文章不少,這裏再也不詳細描述,須要注意的是:將須要動態加載的類放到獨立的jar文件中,從一開始就經過動態加載方式加載,不要放到主進程的jar包中,那樣會被默認加載器加載,會致使在更新後沒法從新加載。編程

2.1. 主項目代碼/模塊

此模塊放不須要動態加載的類。緩存

2.1.1. HotClassLoader.java

用於實現動態記載功能。ide

import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

// 熱加載器
public class HotClassLoader {
    private static final long LOADER_INTERVAL = 3;
    // 指向動態加載module的jar文件
    private static final String HOT_UPDATE_JAR_PATH = "D:\\ClassLoaderDemo-Service\\target\\ClassLoaderDemo-Service-1.0-SNAPSHOT.jar";

    static URLClassLoader classLoader;         //類加載器
    private static long lastModifiedTime = 0;  // jar文件最後更新時間

    // 開始監聽jar文件是否有更新
    public void startListening() {
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
        scheduledExecutorService.scheduleAtFixedRate(()->{
            if (isHotUpdate()) {
                reload();
            }
        }, 0, LOADER_INTERVAL, TimeUnit.SECONDS);
    }

    // 動態獲取新實例,注意返回值可能爲null,調用者要加判斷
    public static Object newInstance(String className) {
        if (classLoader != null) {
            try {
                synchronized (HotClassLoader.class) {
                    Object newInstance = Class.forName(className, true, classLoader).newInstance();
                    return newInstance;
                }
            } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    // 判斷是否有更新
    private boolean isHotUpdate() {
        File hotLoaderFile = new File(HOT_UPDATE_JAR_PATH);
        boolean isHotUpdate = false;
        if (hotLoaderFile.exists()) {
            long newModifiedTime = hotLoaderFile.lastModified();
            isHotUpdate = lastModifiedTime != newModifiedTime;
            lastModifiedTime = newModifiedTime;
        } else {
            System.out.println(hotLoaderFile.getAbsolutePath() + " is not found.");
        }

        System.out.println("isHotUpdate:" + isHotUpdate);
        return isHotUpdate;
    }

    // 從新加載jar文件
    private void reload() {
        File jarPath = new File(HOT_UPDATE_JAR_PATH);
        System.out.println("jar lastModified xxxxxxxxxxxxxxxxxx: " + jarPath.lastModified());

        if (jarPath.exists()) {
            try {
                synchronized (HotClassLoader.class) {
                    classLoader = new URLClassLoader(new URL[]{jarPath.toURI().toURL()});
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        } else {
            System.out.println("Hot update jar is not found.");
        }
    }
}
複製代碼

2.1.2. Service.java

模擬的動態加載類接口。函數

package com.javageektour.classloaderdemo;

public interface Service {
    void printVersion();
}
複製代碼

2.1.3. HotLoadTest.java

測試類。post

package com.javageektour.classloaderdemo;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class HotLoadTest {
    public static void main(String[] args) {
        HotClassLoader hotClassLoader = new HotClassLoader();
        hotClassLoader.startListening();

        // 休眠一會,等加載完
        sleep(3000);
        mockCaller();
        sleep(50000000);
    }

    // 模擬調用者
    private static void mockCaller() {
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
        scheduledExecutorService.scheduleAtFixedRate(()->{
            try {
                Service mockService = (Service) HotClassLoader.newInstance("com.javageektour.classloaderdemo.MockService");
                mockService.printVersion();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, 0, 5, TimeUnit.SECONDS);
    }

    private static void sleep(long timeMillis) {
        try {
            Thread.sleep(timeMillis);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}
複製代碼

2.2. 動態加載項目/模塊

須要動態加載的類放到這個模塊中,甚至能夠按照業務劃分多個模塊。測試

注意這個module須要編譯依賴主模塊的接口類。優化

2.2.1. MockService.java

package com.javageektour.classloaderdemo;

public class MockService implements Service {
    @Override
    public void printVersion() {
        System.out.println("11.0");
    }
}
複製代碼

2.3. 驗證過程

1.編譯動態加載模塊,生成jar文件,並修改主模塊中測試程序的jar文件路徑。
2.啓動測試demo,待打印出版本號後,修改MockService.java中的版本號從新生成jar文件。
3.等待一會打印新的版本號。ui

輸出日誌:

isHotUpdate:true
jar lastModified xxxxxxxxxxxxxxxxxx: 1587832288706
isHotUpdate:false
1.0
isHotUpdate:false
1.0
isHotUpdate:false
isHotUpdate:true
jar lastModified xxxxxxxxxxxxxxxxxx: 1587832303617
2.0
isHotUpdate:false
複製代碼

3. 總結

  1. ClassLoader是Java基礎知識點,應大概瞭解和掌握。
  2. 在某些SLA要求高,不能中止服務的場景下,經過熱加載打補丁也算是一種解決辦法,但要提早作好規劃和設計。
  3. Android動態加載apk邏輯實際至關複雜,要考慮下載,安裝,加載,打點分析等等一系列操做,熱加載只是其中很小的一步,真正要作好,尚有不少問題要考慮和解決。

.end


<--閱過留痕,左邊點贊!

相關文章
相關標籤/搜索