当前位置: 首页 > news >正文

自定义classload实现热加载案例

代码效果:
实现热加载controller … 仅controller、其他不行!热加载有局限


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.Method;
import java.nio.file.*;
import java.util.Map;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicLong;/*** 优化的热加载管理器** 改进点:* 1. ClassLoader内存泄漏修复* 2. 打破双亲委派,避免类冲突* 3. 原子化的加载/卸载操作* 4. 文件稳定性检测* 5. 版本管理* 6. 完整的资源清理* 7. 异常处理优化*/
@Component
public class OptimizedHotSwapManager {@Autowiredprivate ApplicationContext context;@Autowiredprivate RequestMappingHandlerMapping handlerMapping;@Value("${hotswap.path:D:/app/hotswap}")private String hotswapPath;@Value("${hotswap.base.package:org.example}")private String basePackage;// 使用ConcurrentHashMap存储已加载的Controllerprivate final Map<String, LoadedController> cache = new ConcurrentHashMap<>();// 版本计数器private final AtomicLong versionCounter = new AtomicLong(0);private ExecutorService fileWatchExecutor;private ScheduledExecutorService cleanupExecutor;private volatile boolean running = true;@PostConstructpublic void start() {File dir = new File(hotswapPath);if (!dir.exists()) {dir.mkdirs();log("创建目录: " + dir.getAbsolutePath());}log("\n========================================");log("✓ 热加载已启动(优化版)");log("✓ 监控目录: " + dir.getAbsolutePath());log("✓ 基础包: " + basePackage);log("✓ 使用方法: 复制.class到此目录");log("✓ 支持包结构: com/example/XXX.class");log("========================================\n");// 初始化执行器fileWatchExecutor = Executors.newSingleThreadExecutor(r -> {Thread t = new Thread(r, "HotSwap-FileWatch");t.setDaemon(true);return t;});cleanupExecutor = Executors.newScheduledThreadPool(1, r -> {Thread t = new Thread(r, "HotSwap-Cleanup");t.setDaemon(true);return t;});// 定期清理未使用的ClassLoader(帮助GC)cleanupExecutor.scheduleAtFixedRate(this::cleanupUnusedClassLoaders,5, 5, TimeUnit.MINUTES);// 扫描已存在的文件log(">>> 开始扫描已存在的class文件...");scanExisting();// 启动文件监控log(">>> 启动文件监控...");startWatch();log(">>> 文件监控已就绪\n");}@PreDestroypublic void stop() {running = false;log(">>> 开始关闭热加载...");// 卸载所有Controllercache.keySet().forEach(this::unloadController);// 关闭执行器shutdownExecutor(fileWatchExecutor, "FileWatch");shutdownExecutor(cleanupExecutor, "Cleanup");log("✓ 热加载已关闭");}private void shutdownExecutor(ExecutorService executor, String name) {if (executor != null) {executor.shutdown();try {if (!executor.awaitTermination(3, TimeUnit.SECONDS)) {executor.shutdownNow();}} catch (InterruptedException e) {executor.shutdownNow();Thread.currentThread().interrupt();}}}// ==================== 文件扫描 ====================private void scanExisting() {File root = new File(hotswapPath);int count = scanDir(root, root);log("    扫描完成: 发现 " + count + " 个.class文件\n");}private int scanDir(File dir, File root) {File[] files = dir.listFiles();if (files == null) return 0;int count = 0;for (File f : files) {if (f.isDirectory()) {count += scanDir(f, root);} else if (f.getName().endsWith(".class")) {String className = getClassName(f, root);if (className != null) {log("    发现: " + className);loadController(className);count++;}}}return count;}// ==================== 文件监控 ====================private void startWatch() {fileWatchExecutor.submit(() -> {try (WatchService ws = FileSystems.getDefault().newWatchService()) {Path watchPath = Paths.get(hotswapPath);registerAll(watchPath, ws);while (running) {WatchKey key;try {key = ws.poll(1, TimeUnit.SECONDS);if (key == null) continue;} catch (InterruptedException e) {Thread.currentThread().interrupt();break;}Path dir = (Path) key.watchable();for (WatchEvent<?> event : key.pollEvents()) {WatchEvent.Kind<?> kind = event.kind();if (kind == StandardWatchEventKinds.OVERFLOW) {log("[警告] 事件溢出");continue;}Path filename = (Path) event.context();Path fullPath = dir.resolve(filename);// 注册新目录if (kind == StandardWatchEventKinds.ENTRY_CREATE &&Files.isDirectory(fullPath)) {try {registerAll(fullPath, ws);} catch (Exception e) {log("[错误] 注册目录失败: " + e.getMessage());}}// 处理class文件if (fullPath.toString().endsWith(".class")) {handleClassFileEvent(fullPath.toFile(), kind);}}if (!key.reset()) {log("[警告] WatchKey不再有效");break;}}} catch (Exception e) {log("[错误] 文件监控异常: " + e.getMessage());e.printStackTrace();}});}private void registerAll(Path start, WatchService ws) throws IOException {Files.walk(start).filter(Files::isDirectory).forEach(p -> {try {p.register(ws,StandardWatchEventKinds.ENTRY_CREATE,StandardWatchEventKinds.ENTRY_MODIFY,StandardWatchEventKinds.ENTRY_DELETE);} catch (Exception e) {log("[警告] 注册目录失败: " + p);}});}private void handleClassFileEvent(File file, WatchEvent.Kind<?> kind) {try {// 等待文件稳定if (!waitForFileStable(file, kind)) {return;}String className = getClassName(file, new File(hotswapPath));if (className == null) return;log("\n>>> 文件事件: " + kind.name());log("    类名: " + className);if (kind == StandardWatchEventKinds.ENTRY_DELETE) {unloadController(className);} else {loadController(className);}} catch (Exception e) {log("[错误] 处理文件事件失败: " + e.getMessage());}}// 等待文件写入完成private boolean waitForFileStable(File file, WatchEvent.Kind<?> kind)throws InterruptedException {if (kind == StandardWatchEventKinds.ENTRY_DELETE) {return true;}for (int i = 0; i < 5; i++) {if (!file.exists()) return false;long size1 = file.length();Thread.sleep(100);long size2 = file.length();if (size1 == size2 && size1 > 0) {return true;}}log("[警告] 文件不稳定: " + file.getName());return false;}// ==================== 加载/卸载 ====================private void loadController(String className) {// 使用compute确保原子性cache.compute(className, (key, oldController) -> {try {// 先卸载旧版本if (oldController != null) {log("    [加载] 检测到旧版本,先卸载");unloadInternal(oldController);}// 加载新版本return loadInternal(className);} catch (Exception e) {log("[错误] 加载失败: " + className);log("    原因: " + e.getMessage());e.printStackTrace();return null; // 加载失败,从缓存中移除}});}private LoadedController loadInternal(String className) throws Exception {log("    [加载] 开始加载: " + className);// 创建ClassLoaderHotSwapClassLoader loader = new HotSwapClassLoader(hotswapPath, basePackage, getClass().getClassLoader());// 加载类Class<?> clazz = loader.loadClass(className);log("    [加载] Class加载完成");// 检查是否是Controllerif (!isController(clazz)) {log("    [加载] 跳过: 不是Controller类");return null;}// 创建实例Object bean = clazz.getDeclaredConstructor().newInstance();log("    [加载] 实例创建成功");// 注册到Springlong version = versionCounter.incrementAndGet();String beanName = registerBean(clazz, bean, version);log("    [加载] Bean注册完成: " + beanName);// 注册映射int mappings = registerMappings(bean, clazz);log("    [加载] 映射注册完成: " + mappings + "个");LoadedController controller = new LoadedController(beanName, bean, clazz, loader, version);log("✓✓✓ 加载成功: " + className + " v" + version + " ✓✓✓\n");return controller;}private void unloadController(String className) {cache.computeIfPresent(className, (key, controller) -> {unloadInternal(controller);return null; // 从缓存中移除});}private void unloadInternal(LoadedController controller) {try {log("    [卸载] 开始卸载: " + controller.beanName);// 注销映射unregisterMappings(controller.bean, controller.clazz);log("    [卸载] 映射已注销");// 注销BeanunregisterBean(controller.beanName);log("    [卸载] Bean已注销");// 清理ClassLoader引用controller.classLoader = null;log("✓ 卸载成功: " + controller.beanName);} catch (Exception e) {log("[错误] 卸载失败: " + e.getMessage());e.printStackTrace();}}// ==================== Spring集成 ====================private String registerBean(Class<?> clazz, Object bean, long version) {DefaultListableBeanFactory factory =(DefaultListableBeanFactory) context.getAutowireCapableBeanFactory();String name = clazz.getSimpleName() + "_v" + version + "_" +System.currentTimeMillis();factory.registerSingleton(name, bean);factory.autowireBean(bean);// 调用@PostConstruct方法(如果有)try {factory.initializeBean(bean, name);} catch (Exception e) {log("[警告] 初始化Bean失败: " + e.getMessage());}return name;}private void unregisterBean(String name) {DefaultListableBeanFactory factory =(DefaultListableBeanFactory) context.getAutowireCapableBeanFactory();if (factory.containsSingleton(name)) {// 调用@PreDestroy方法try {factory.destroyBean(name, factory.getSingleton(name));} catch (Exception e) {log("[警告] 销毁Bean失败: " + e.getMessage());}factory.destroySingleton(name);}}private int registerMappings(Object bean, Class<?> clazz) {int count = 0;Method[] methods = clazz.getDeclaredMethods();for (Method m : methods) {try {Method getMappingMethod = RequestMappingHandlerMapping.class.getDeclaredMethod("getMappingForMethod", Method.class, Class.class);getMappingMethod.setAccessible(true);RequestMappingInfo info = (RequestMappingInfo) getMappingMethod.invoke(handlerMapping, m, clazz);if (info != null) {handlerMapping.registerMapping(info, bean, m);count++;}} catch (NoSuchMethodException e) {// 方法没有映射,正常情况} catch (Exception e) {log("[警告] 注册映射失败: " + m.getName() + " - " + e.getMessage());}}return count;}private void unregisterMappings(Object bean, Class<?> clazz) {for (Method m : clazz.getDeclaredMethods()) {try {Method getMappingMethod = RequestMappingHandlerMapping.class.getDeclaredMethod("getMappingForMethod", Method.class, Class.class);getMappingMethod.setAccessible(true);RequestMappingInfo info = (RequestMappingInfo) getMappingMethod.invoke(handlerMapping, m, clazz);if (info != null) {handlerMapping.unregisterMapping(info);}} catch (Exception e) {// 忽略}}}// ==================== 工具方法 ====================private boolean isController(Class<?> clazz) {return clazz.isAnnotationPresent(org.springframework.stereotype.Controller.class) ||clazz.isAnnotationPresent(RestController.class);}private String getClassName(File file, File root) {try {String path = root.toURI().relativize(file.toURI()).getPath();// 移除.class后缀,并将所有路径分隔符转为点号String className = path.replace(".class", "").replace('/', '.').replace('\\', '.');// 确保没有前后多余的点号if (className.startsWith(".")) {className = className.substring(1);}if (className.endsWith(".")) {className = className.substring(0, className.length() - 1);}return className;} catch (Exception e) {log("[错误] 解析类名失败: " + e.getMessage());return null;}}private void cleanupUnusedClassLoaders() {// 提示JVM进行垃圾回收System.gc();log("[清理] 已触发垃圾回收,清理未使用的ClassLoader");}private void log(String msg) {System.out.println("[HotSwap] " + msg);}// ==================== 内部类 ====================/*** 已加载的Controller信息*/static class LoadedController {final String beanName;final Object bean;final Class<?> clazz;ClassLoader classLoader; // 可以置null帮助GCfinal long version;LoadedController(String beanName, Object bean, Class<?> clazz,ClassLoader classLoader, long version) {this.beanName = beanName;this.bean = bean;this.clazz = clazz;this.classLoader = classLoader;this.version = version;}}/*** 优化的ClassLoader** 改进:* 1. 打破双亲委派,优先加载自己的类* 2. 避免类冲突* 3. 支持依赖类加载*/static class HotSwapClassLoader extends ClassLoader {private final String classPath;private final String basePackage;public HotSwapClassLoader(String classPath, String basePackage,ClassLoader parent) {super(parent);this.classPath = classPath;this.basePackage = basePackage;}@Overridepublic Class<?> loadClass(String name) throws ClassNotFoundException {// 对于基础包内的类,打破双亲委派,优先自己加载if (name.startsWith(basePackage)) {synchronized (getClassLoadingLock(name)) {// 检查是否已加载Class<?> c = findLoadedClass(name);if (c == null) {try {// 尝试自己加载c = findClass(name);} catch (ClassNotFoundException e) {// 加载失败,委托给父加载器c = super.loadClass(name);}}return c;}}// 其他类使用默认委派机制return super.loadClass(name);}@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {try {String fileName = classPath + File.separator +name.replace('.', File.separatorChar) + ".class";File file = new File(fileName);if (!file.exists()) {throw new ClassNotFoundException("文件不存在: " + fileName);}byte[] bytes = loadClassData(fileName);return defineClass(name, bytes, 0, bytes.length);} catch (IOException e) {throw new ClassNotFoundException(name, e);}}private byte[] loadClassData(String fileName) throws IOException {try (FileInputStream fis = new FileInputStream(fileName);ByteArrayOutputStream baos = new ByteArrayOutputStream()) {byte[] buffer = new byte[4096];int len;while ((len = fis.read(buffer)) != -1) {baos.write(buffer, 0, len);}return baos.toByteArray();}}}
}
http://www.dtcms.com/a/536349.html

相关文章:

  • 上海网站建设seo站霸网络网站建设推销拜访客户怎么开头
  • Spring Bean的生命周期 第二次思考
  • HttpServletResponse下载文件
  • vue3的路由详解
  • Spring Boot 生产就绪特性浅析(一)
  • 如何做彩票网站信息手机上打开html的软件
  • 【图像处理基石】图像对比度增强入门:从概念到实战(Python+OpenCV)
  • 网站建设公司六安全国连锁装修公司加盟
  • 直播互动小程序端Web运营端接入指南
  • Java—抽象类
  • 坛墨网站建设wordpress 邀请
  • idc网站模版百度提交
  • 树莓派3B+降低功耗及恢复脚本
  • 开源项目解读4-高性能并发缓存库Ristretto
  • 《微信小程序》第五章:登录-API封装
  • MYSQL数据库入门操作
  • 青岛建设集团网站101工业设计有限公司
  • wordpress 网站上传到服务器错误怎么建设手机网站
  • MySQL 下载、安装及配置教程(Msi安装)
  • AWS CloudTrail 可观测最佳实践
  • 商城网站设计公司十大奢侈品排名
  • 建设部网站从哪登陆网站建设学什么书
  • STM32学习(MCU控制)(NVIC)
  • C++11新特性:强类型枚举/编译期断言/元组
  • 个人做网站的注意事项动漫做暧视频网站
  • 高并发电商架构设计与落地:从微服务拆分到全链路优化
  • 无人机电调芯片替换全解析:从 AM32 架构到 STM32F072、GD32E230 与 AT32F421 的实战对比
  • 郴州市建设网站ps软件下载免费中文版
  • 选择扬中网站建设做电商一个月可以赚多少钱
  • flutter开发的音乐搜索app