相关阅读:
JAVA基础(一)简单、透彻理解内部类和静态内部类
JAVA基础(二)内存优化-使用Java引用做缓存网上描述ClassLoader加载的文章很多,这里不再详细描述,需要注意的是:将需要动态加载的类放到独立的jar文件中,从一开始就通过动态加载方式加载,不要放到主进程的jar包中,那样会被默认加载器加载,会导致在更新后无法重新加载。
此模块放不需要动态加载的类。
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();
}
}
}
复制代码
需要动态加载的类放到这个模块中,甚至可以按照业务划分多个模块。
注意这个module需要编译依赖主模块的接口类。
2.2.1. MockService.java
package com.javageektour.classloaderdemo;
public class MockService implements Service {
@Override
public void printVersion() {
System.out.println("11.0");
}
}
复制代码
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 复制代码