1. 應用場景
- 修復bug,不需要重啓服務,動態加載修改的bug類。
- 動態升級,在android系統中,可以通過動態加載APK繞過應用市場的的升級策略,自行定製升級策略。
2. 例子
網上描述ClassLoader加載的文章很多,這裏不再詳細描述,需要注意的是:將需要動態加載的類放到獨立的jar文件中,從一開始就通過動態加載方式加載,不要放到主進程的jar包中,那樣會被默認加載器加載,會導致在更新後無法重新加載。
2.1. 主項目代碼/模塊
此模塊放不需要動態加載的類。
2.1.1. HotClassLoader.java
用於實現動態記載功能。
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
測試類。
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.等待一會打印新的版本號。
輸出日誌:
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. 總結
- ClassLoader是Java基礎知識點,應大概瞭解和掌握。
- 在某些SLA要求高,不能停止服務的場景下,通過熱加載打補丁也算是一種解決辦法,但要提前做好規劃和設計。
- Android動態加載apk邏輯實際相當複雜,要考慮下載,安裝,加載,打點分析等等一系列操作,熱加載只是其中很小的一步,真正要做好,尚有很多問題要考慮和解決。
.end