热加载 hotswap-classloader

简介

什么是 hotswap-classloader

hotswap-classloaderopen in new window 是由笔者 litongjava 开发的一款 Java 动态类加载器。其核心功能是在 Java 应用运行时动态地更换或更新类定义,而无需重启整个 JVM。这种热替换(Hot Swapping)能力对于开发过程中的快速迭代和测试尤为有用,因为它显著减少了等待应用重启的时间。

为什么将 hotswap-classloadertio-boot 结合使用?

hotswap-classloadertio-boot 结合使用,可以为 Java 网络应用开发带来以下关键优势:

  1. 快速迭代和测试:通过使用 hotswap-classloader,开发者可以在不重启服务器的情况下实时更新类文件,实现快速迭代和即时测试。
  2. 提高开发效率:减少了重启应用程序所需的时间,开发者可以更加专注于代码的编写和改进,从而提高工作效率。
  3. 支持敏捷开发:在敏捷开发模式下,频繁的更改和测试是常态。hotswap-classloader 的动态加载能力使得这一过程更加流畅和高效。

总的来说,结合使用 hotswap-classloadertio-boot 不仅提高了开发效率,而且增强了网络应用开发的灵活性和便利性。这对于希望快速迭代和改进其网络应用的开发团队来说,是一个非常有价值的组合。

整合热加载

整合热加载的步骤

  1. 添加依赖

    在项目的 pom.xml 文件中添加以下依赖:

    <dependency>
      <groupId>com.litongjava</groupId>
      <artifactId>hotswap-classloader</artifactId>
      <!-- https://central.sonatype.com/artifact/com.litongjava/hotswap-classloader -->
      <version>${hotswap-classloader.version}</version>
    </dependency>
    
  2. 使用 TioApplicationWrapper 启动服务

    在启动类中使用 TioApplicationWrapper 来启动应用:

    import com.litongjava.hotswap.wrapper.tio.boot.TioApplicationWrapper;
    import com.litongjava.jfinal.aop.annotation.AComponentScan;
    
    @AComponentScan
    public class HelloApp {
      public static void main(String[] args) {
        long start = System.currentTimeMillis();
        TioApplicationWrapper.run(HelloApp.class, args);
        long end = System.currentTimeMillis();
        System.out.println((end - start) + "ms");
      }
    }
    
  3. 启动热加载

    • 在启动类的程序参数(Program arguments)中添加启动参数 --mode=dev,或者在配置文件中添加对应的配置。
    • TioApplicationWrapper 会自动判断是否启用热加载。判断逻辑是:如果 ClassLoader 的 URL 中包含 /target/classes/,则是开发环境,开启热加载;否则不是生产环境,不开启

测试热加载效果

  • 在 Eclipse IDE 中:保存文件即可触发热加载,实时查看效果。
  • 在 IDEA 环境中:需要在运行时手动编译(Build -> Recompile)文件才能看到效果。

日志示例

```log
2024-09-15 13:48:09.531 [main] INFO  c.l.h.w.t.b.TioApplicationWrapper.runDev:75 - start hotswap watcher:Thread[HotSwapWatcher,10,main]
2024-09-15 13:48:09.540 [main] INFO  c.l.h.w.t.b.TioApplicationWrapper.runDev:82 - new hotswap class loader:com.litongjava.hotswap.classloader.HotSwapClassLoader@7cf10a6f
2024-09-15 13:48:09.578 [main] INFO  c.l.t.u.e.EnvUtils.load:171 - app.env:null
2024-09-15 13:48:09.611 [main] INFO  c.l.j.a.s.ComponentScanner.findClasses:72 - resource:file:/E:/code/java/project-litongjava/tio-boot-web-hello/target/classes/com/litongjava/tio/web/hello
2024-09-15 13:48:09.671 [main] INFO  c.l.t.u.Threads.getTioExecutor:93 - new worker thead pool:com.litongjava.tio.utils.thread.pool.SynThreadPoolExecutor@612679d6[Running, pool size = 1, active threads = 1, queued tasks = 0, completed tasks = 0]
2024-09-15 13:48:09.676 [main] INFO  c.l.t.u.Threads.getGroupExecutor:51 - new group thead pool:java.util.concurrent.ThreadPoolExecutor@52f759d7[Running, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 0]

2024-09-15 13:48:09.698 [main] INFO  c.l.t.b.c.TioApplicationContext.run:278 - init:57(ms),scan class:17(ms),config:50(ms),server:13(ms),http route:16(ms)
2024-09-15 13:48:09.698 [main] INFO  c.l.t.b.c.TioApplicationContext.printUrl:295 - port:80
http://localhost
363ms

2024-09-15 13:48:46.892 [pool-1-thread-1] INFO  c.l.h.w.HotSwapWatcher.process:156 - watch event ENTRY_MODIFY,IndexController.class
2024-09-15 13:48:46.893 [pool-1-thread-1] INFO  c.l.h.w.HotSwapWatcher.process:165 - restart server:com.litongjava.hotswap.wrapper.tio.boot.TioBootRestartServer@126afc98
loading
2024-09-15 13:48:46.893 [pool-1-thread-1] INFO  c.l.t.b.c.TioApplicationContext.close:317 - stop server
2024-09-15 13:48:46.894 [tio-group-2] INFO  c.l.t.s.AcceptCompletionHandler.failed:132 - The server will be shut down and no new requests will be accepted:0.0.0.0:80
2024-09-15 13:48:46.895 [pool-1-thread-1] INFO  c.l.t.u.Threads.close:177 - shutdown group thead pool:java.util.concurrent.ThreadPoolExecutor@52f759d7[Terminated, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 7]
2024-09-15 13:48:46.896 [pool-1-thread-1] INFO  c.l.t.u.Threads.close:188 - shutdown worker thead pool:com.litongjava.tio.utils.thread.pool.SynThreadPoolExecutor@612679d6[Terminated, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 2]
2024-09-15 13:48:46.896 [pool-1-thread-1] INFO  c.l.t.s.TioServer.stop:141 - 0.0.0.0:80 stopped
2024-09-15 13:48:46.898 [pool-1-thread-1] INFO  c.l.h.w.t.b.TioBootRestartServer.restart:33 - new classLoader:com.litongjava.hotswap.classloader.HotSwapClassLoader@5ff60e5f
2024-09-15 13:48:46.904 [pool-1-thread-1] INFO  c.l.t.u.e.EnvUtils.load:171 - app.env:null
2024-09-15 13:48:46.904 [pool-1-thread-1] INFO  c.l.j.a.s.ComponentScanner.findClasses:72 - resource:file:/E:/code/java/project-litongjava/tio-boot-web-hello/target/classes/com/litongjava/tio/web/hello
2024-09-15 13:48:46.912 [pool-1-thread-1] INFO  c.l.t.u.Threads.getTioExecutor:93 - new worker thead pool:com.litongjava.tio.utils.thread.pool.SynThreadPoolExecutor@6bb1b24e[Running, pool size = 1, active threads = 1, queued tasks = 0, completed tasks = 0]
2024-09-15 13:48:46.913 [pool-1-thread-1] INFO  c.l.t.u.Threads.getGroupExecutor:51 - new group thead pool:java.util.concurrent.ThreadPoolExecutor@6c0d059c[Running, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 0]

2024-09-15 13:48:46.917 [pool-1-thread-1] INFO  c.l.t.b.c.TioApplicationContext.run:278 - init:5(ms),scan class:3(ms),config:5(ms),server:1(ms),http route:4(ms)
2024-09-15 13:48:46.917 [pool-1-thread-1] INFO  c.l.t.b.c.TioApplicationContext.printUrl:295 - port:80
http://localhost
Loading complete in 24 ms (^_^)

热加载的实现原理

1. 动态类加载

hotswap-classloader 通过自定义 ClassLoader,实现了在运行时动态加载和替换类的功能。当检测到 class 文件发生变化时,创建一个新的 ClassLoader 来加载更新后的类定义,并使用反射机制重新初始化应用上下文。

2. 文件监控

使用 Java NIO 的 WatchService 监控 class 文件的变动。当检测到文件修改事件时,触发热加载流程。

3. 自定义线程池

由于 NIO2 的默认线程池不支持热加载,为了避免线程池中的线程持有旧的 ClassLoader 导致类无法卸载的问题,hotswap-classloader 使用了自定义的线程池 tio-group。在热加载时,旧的线程池会被安全地关闭,新的线程池会随新的 ClassLoader 一起创建。

4. 生产环境优化

在生产环境中,TioApplicationWrapper 会自动检测运行模式,避免加载热加载相关的功能,从而不引入额外的性能损耗。生产环境中,仍然使用 NIO 的默认线程池,确保性能和稳定性。

更多使用说明

有关热加载的更多使用细节和高级配置,请参考官方文档:

https://github.com/litongjava/hotswap-classloaderopen in new window

部署注意事项

  • 禁用热加载:在部署到生产环境时,不要在程序参数中添加 --mode=devTioApplicationWrapper 会调用 TioApplication 来启动项目,不会启用热加载功能。
  • 性能考虑:理论上,生产环境不会有多余的性能损耗,因为 TioApplicationWrapper 仅在启动时判断了运行模式和 classpath 路径。

总结

通过整合 hotswap-classloader,开发者可以在不重启应用的情况下实时更新代码,大大提高了开发效率。同时,得益于对生产环境的优化处理,热加载机制不会对线上应用的性能和稳定性造成影响。

如果是 Eclipse IDE,保持一个文件即可测试加载效果,如果是 IDEA 环境需要再运行时手动编译(Build-->Recompile)文件才可以看到效果