相关阅读:
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 复制代码