• 1. 概述
  • 2. spring-boot-devtools
  • 3. IDEA 热部署
  • 4. Jrebel
  • 666. 彩蛋

本文在提供完整代码示例,可见 https://github.com/YunaiV/SpringBoot-Labs 的 lab-48-hot-swap 目录。

原创不易,给点个 Star 嘿,一起冲鸭!

图片


1. 概述

在日常开发中,我们需要经常修改 Java 代码,手动重启项目,查看修改后的效果。如果在项目小时,重启速度比较快,等待的时间是较短的。但是随着项目逐渐变大,重启的速度变慢,等待时间 1-2 min 是比较常见的。

这样就导致我们开发效率降低,影响我们的下班时间,哈哈哈~那么是否有方式能够实现,在我们修改完 Java 代码之后,能够不重启项目呢?

答案是有的,通过热部署的方式。并且实现的方式还是非常多,艿艿在本文就会为胖友一一展示。

旁白君:严格来说,应该叫 HotSwap 的方式,翻译成中文会有热部署、热更新、热替换、热加载等等多种。这里,我们就采用大家可能说的比较多的翻译,热部署

为了演示方便,胖友可以参考 lab-48-demo 项目,搭建一个简单的 Spring Boot 项目,提供了一个简单的 HTTP API。如下图所示:图片

友情提示:不要直接通过克隆 https://github.com/YunaiV/SpringBoot-Labs 来使用该项目,实在太大了!

并且,我们下面我们所有的演示,都是在宇宙无敌 Java 开发工具 IDEA 中进行。

2. spring-boot-devtools

spring-boot-devtools 是 Spring Boot 提供的开发者工具,它会监控当前应用所在的 classpath 下的文件发生变化,进行自动重启

注意,spring-boot-devtools 并没有采用热部署的方式,而是一种较快的重启方式。其官方文档解释如下:

FROM 《Spring Boot 2.X 中文文档 —— 开发者工具》

Spring Boot 通过使用两个类加载器来提供了重启技术。

  • 不改变的类(例如,第三方 jar)被加载到 base 类加载器中。
  • 经常处于开发状态的类被加载到 restart 类加载器中。

当应用重启时,restart 类加载器将被丢弃,并重新创建一个新的。这种方式意味着应用重启比冷启动要快得多,因为省去 base 类加载器的处理步骤,并且可以直接使用。

如果您觉得重启还不够快,或者遇到类加载问题,您可以考虑如 ZeroTurnaround 的 JRebel 等工具。他们是通过在加载类时重写类来加快重新加载。

在项目中,我们需要在 pom.xml 中,引入 spring-boot-devtools 依赖如下:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <optional>true</optional> <!-- 可选 -->
</dependency>

2.1 演示

下面,我们来演示下 spring-boot-devtools 的使用。

① Run 或者 Debug 运行 Spring Boot 应用。

使用浏览器,访问 http://127.0.0.1:8080/demo/echo 接口,返回结果为 "echo"

② 修改 DemoController 的 #echo() 方法,设置返回值为 "none"

【关键】 我们现在仅仅需要修改了 Java 代码,需要重新编译下代码。点击 IDEA 的菜单 Build -> Build Project手动进行编译。如下图所示:图片

友情提示:如果胖友嫌弃鼠标操作太慢,可以使用 Build Project 的快捷键:

  • Mac:Command + F9
  • Windows:Ctrl + F9

此时,IDEA 控制台会看到 Spring Boot 重新启动的日志如下:

2020-02-09 09:22:52.082  INFO 36495 --- [      Thread-10] o.s.s.concurrent.ThreadPoolTaskExecutor  : Shutting down ExecutorService 'applicationTaskExecutor'

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '
_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.2.4.RELEASE)

2020-02-09 09:22:52.195  INFO 36495 --- [  restartedMain] cn.iocoder.demo03.Demo03Application      : Starting Demo03Application on MacBook-Pro-8 with PID 36495 (/Users/yunai/Downloads/demo03/target/classes started by yunai in /Users/yunai/Downloads/demo03)
2020-02-09 09:22:52.195  INFO 36495 --- [  restartedMain] cn.iocoder.demo03.Demo03Application      : No active profile set, falling back to default profiles: default
2020-02-09 09:22:52.335  INFO 36495 --- [  restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2020-02-09 09:22:52.336  INFO 36495 --- [  restartedMain] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2020-02-09 09:22:52.336  INFO 36495 --- [  restartedMain] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.30]
2020-02-09 09:22:52.342  INFO 36495 --- [  restartedMain] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2020-02-09 09:22:52.342  INFO 36495 --- [  restartedMain] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 145 ms
2020-02-09 09:22:52.382  INFO 36495 --- [  restartedMain] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService '
applicationTaskExecutor'
2020-02-09 09:22:52.409  INFO 36495 --- [  restartedMain] o.s.b.d.a.OptionalLiveReloadServer       : LiveReload server is running on port 35729
2020-02-09 09:22:52.418  INFO 36495 --- [  restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path '
'
2020-02-09 09:22:52.419  INFO 36495 --- [  restartedMain] cn.iocoder.demo03.Demo03Application      : Started Demo03Application in 0.244 seconds (JVM running for 169.162)
2020-02-09 09:22:52.420  INFO 36495 --- [  restartedMain] .ConditionEvaluationDeltaLoggingListener : Condition evaluation unchanged