热部署
一句话定位:
spring-boot-devtools通过双 ClassLoader 机制和文件变更监听,在开发阶段实现代码修改后的自动重启与静态资源实时刷新,将传统"修改-停止-启动"的分钟级反馈循环缩短到秒级。
定义与作用
在传统 SSM 开发中,修改一个 Service 方法后需要:停止 Tomcat(5 秒)→ 重新编译(10 秒)→ 启动 Tomcat(30 秒)→ 验证效果。一次微小调整可能消耗近一分钟。这种长反馈循环严重拖慢开发效率。
Spring Boot 的 spring-boot-devtools 模块专为开发环境设计,提供两项核心能力:
| 能力 | 说明 | 触发条件 |
|---|---|---|
| 自动重启(Auto Restart) | 代码变更后自动重启 Spring 容器 | classpath 下的类文件发生变化 |
| LiveReload | 浏览器自动刷新页面 | 静态资源(HTML/CSS/JS)发生变化 |
核心设计:devtools 使用两个 ClassLoader——
restart ClassLoader加载项目自身的类(变化频繁),base ClassLoader加载第三方依赖(相对稳定)。重启时只需丢弃并重建restart ClassLoader,大量依赖无需重新加载,因此比冷启动快得多。
适用位置与常用属性
spring-boot-devtools 作为依赖引入,无需代码注解。所有行为通过配置控制。
依赖引入(pom.xml)
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
<scope>runtime</scope>
</dependency>
</dependencies>
<optional>true</optional>的作用:确保其他项目依赖本模块时,不会传递引入 devtools。避免 devtools 被带入生产环境。
常用配置属性
# application.yml
spring:
devtools:
restart:
enabled: true # 是否启用自动重启(默认 true)
additional-paths: # 额外监听路径
- src/main/java
- src/main/resources
exclude: # 排除不监听的文件/路径
- static/**
- public/**
poll-interval: 1000 # 轮询间隔(毫秒)
quiet-period: 400 # 静默期,文件变更停止后等待多久触发重启
livereload:
enabled: true # 是否启用 LiveReload(默认 true)
port: 35729 # LiveReload 浏览器插件通信端口
| 属性 | 说明 | 默认值 |
|---|---|---|
spring.devtools.restart.enabled | 自动重启总开关 | true |
spring.devtools.restart.additional-paths | 额外监听目录 | [] |
spring.devtools.restart.exclude | 排除模式(不触发重启) | META-INF/maven/**,META-INF/resources/**,resources/**,static/**,public/**,templates/** |
spring.devtools.restart.poll-interval | 文件系统轮询间隔 | 1000 ms |
spring.devtools.restart.quiet-period | 变更停止后延迟重启 | 400 ms |
spring.devtools.livereload.enabled | LiveReload 开关 | true |
spring.devtools.livereload.port | LiveReload 服务端口 | 35729 |
全局设置:在 Windows 下,可在用户主目录创建
C:\Users\{用户名}\.spring-boot-devtools.properties,设置所有本地项目共享的配置(如spring.devtools.restart.trigger-file=.reloadtrigger)。
核心原理
双 ClassLoader 重启机制
机制解读:
- 应用启动时,devtools 创建两个类加载器:
base加载第三方 JAR,restart加载项目自身类。 - 后台线程持续监视 classpath 文件变化(轮询或事件监听)。
- 当检测到项目类文件(
.class)或配置文件变化时,先销毁旧的restart ClassLoader,再创建新的restart ClassLoader重新加载业务类。 - 调用
SpringApplication.run()重新初始化 Spring 容器和内嵌服务器。由于base ClassLoader未重建,第三方依赖无需重新加载,重启时间显著缩短(通常 2-5 秒,视项目规模而定)。
重启时序流程
完整示例
场景说明
飞翔科技的后端开发小崔正在开发学生成绩查询接口。他频繁调整 StudentService 中的查询逻辑和日志输出,每次手动重启需要 30 秒以上。架构师白歌建议引入 spring-boot-devtools 提升开发效率,要求小崔验证:修改 Service 类后应用能否在数秒内自动重启,且静态资源修改不需要重启。
操作前:无 devtools 的手动重启
<!-- 操作前:pom.xml 中没有 devtools -->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
小崔修改 StudentService.java 后,需要:
- 在 IDEA 中点击停止按钮(或按 Ctrl+C)
- 重新运行
StudentManagementApplication.main() - 等待 Tomcat 启动完成(约 30 秒)
- 刷新浏览器验证
整个反馈周期约 35-40 秒,开发体验极差。
使用该特性的完整代码
步骤一:在 pom.xml 中引入 devtools:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
步骤二:配置 application.yml(可选,使用默认值即可)
spring:
devtools:
restart:
enabled: true
exclude: static/**,public/** # 静态资源变更不触发重启
livereload:
enabled: true
步骤三:保持业务代码正常开发
package com.feixiang.student.service;
import org.springframework.stereotype.Service;
@Service
public class StudentService {
public String queryScore(Long studentId) {
// 小崔正在频繁调整这里的逻辑和日志
return "学生 " + studentId + " 的成绩查询成功(当前时间:" + System.currentTimeMillis() + ")";
}
}
操作后运行结果及分析
小崔修改 StudentService.queryScore() 方法体,在 IDEA 中按 Ctrl+F9(Build Project)编译后,控制台自动输出:
Restarting due to 1 class path change (com/feixiang/student/service/StudentService.class)
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.7.18)
2024-05-20 14:10:25.123 INFO 12345 --- [ restartedMain] c.f.s.StudentManagementApplication : Started StudentManagementApplication in 2.456 seconds
关键日志解读:
Restarting due to 1 class path change:devtools 检测到StudentService.class文件变更,自动触发重启。restartedMain:线程名带有restarted前缀,区别于首次启动的main线程。Started ... in 2.456 seconds:重启耗时约 2.5 秒,比冷启动快 10 倍以上(因为第三方依赖未重新加载)。
小崔立即刷新浏览器验证新逻辑,整个过程无需手动停止或启动。
修改 src/main/resources/static/index.html 后,控制台无重启日志,浏览器通过 LiveReload 自动刷新(需安装 LiveReload 浏览器插件或 IDE 内置支持)。
分析:
- 双 ClassLoader 机制生效:只有
restart ClassLoader重建,Spring Boot 核心库和 Tomcat 仍在base ClassLoader中,无需重新初始化。 exclude: static/**生效:静态资源变更不触发容器重启,避免不必要的重启开销。- LiveReload 生效:前端资源修改实时反馈到浏览器,前后端开发效率均提升。
易错场景与面试考点
易错场景一:生产环境误引入 devtools
<!-- 错误示范:未标记 optional,导致传递依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
后果:如果该模块被其他项目依赖,devtools 会传递到生产环境。生产环境 JAR 中携带 devtools 后,会启动文件监听线程和 LiveReload 服务,造成性能损耗。更严重的是,Spring Boot 1.x/2.x 早期版本存在远程触发重启的安全漏洞(Spring Boot 2.7.x 已修复远程访问,但仍不建议携带)。
正确做法:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
同时确保打包时排除 devtools:
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludeDevtools>true</excludeDevtools>
</configuration>
</plugin>
易错场景二:修改配置文件未触发重启
小崔修改 application.yml 中的 server.port 后,等待重启却未发生。
原因:Spring Boot 的默认配置中,application.yml 本身在 base ClassLoader 或标准资源加载路径中,某些 IDE 的编译行为不会触发 restart ClassLoader 感知的文件变更。此外,部分配置变更即使重启也无法热生效(如端口号变更需全新进程)。
正确做法:对于 application.yml 的修改,若未自动触发,可手动触发一次重启(在 IDEA 中重新 Build)。或使用 spring.devtools.restart.trigger-file 机制,通过创建/修改触发文件强制重启。最根本的端口等配置变更,仍需完全停止并重新启动 JVM。
面试考点
Q:spring-boot-devtools 的自动重启原理是什么?
devtools 使用两个 ClassLoader:
base加载第三方依赖(不变),restart加载项目自身类(常变)。当文件系统检测到 classpath 中的类文件变更时,丢弃旧的restart ClassLoader,创建新的restart ClassLoader重新加载业务类,并重新执行SpringApplication.run()。第三方依赖无需重新加载,因此重启速度远快于冷启动。
Q:为什么 devtools 建议标记为 <optional>true</optional>?
防止 devtools 通过 Maven 传递依赖进入其他模块或生产环境。
optional表示该依赖仅当前模块使用,不会强制传递给依赖本模块的项目。
Q:devtools 会重启内嵌 Tomcat 吗?
不会。Tomcat 由
base ClassLoader加载,在重启过程中保持存活。restart ClassLoader只重建 Spring 容器和 DispatcherServlet 的映射关系,Tomcat 本身继续监听原端口。这也是重启速度快的核心原因之一。
Q:LiveReload 和自动重启有什么区别?
自动重启针对 Java 类文件变更,触发 Spring 容器重新初始化。LiveReload 针对静态资源(HTML/CSS/JS)变更,通过浏览器插件或 WebSocket 通知浏览器刷新页面,不涉及服务器重启。两者互补:改后端代码自动重启,改前端代码实时刷新。