02-spring-boot-devtools自动加载修改后的类实现热更新
技术背景
我们在一个大型项目里,往往启动一次需要加载大量的类和外部资源,如果开发期频繁修改一两个类,甚至是个别方法或语句,重启一次应用代价太大了。导致调试的时间成本极大的增加。
为了解决这个问题,spring boot通过了一个devtools工具库,可以实现项目里的classes文件夹下编译好的类发生修改变动时,自动热更新加载他们,从而实现应用不需要重启,大大的提升了开发效率。
使用方法
比如我们有个类:
@RestControllerpublic class HelloController { @RequestMapping("/hello") public String hello() { return "Hello World, kimmking!"; }}
启动后,执行命令:
% curl http://localhost:8080/helloHello World, kimmking!%
我们想要在修改代码后,不用重启,就实现运行最新的代码。
@RestControllerpublic class HelloController { @RequestMapping("/hello") public String hello() { return "Hello World, devtools!"; }}
在spring-boot项目里添加如下依赖,则可以实现开发环境的热部署。
即不需要重启应用,就能实现修改过的类起作用。
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>runtime</scope><optional>true</optional></dependency>
此时重新import maven依赖,再启动应用。
先执行curl访问一次输出kimmking,修改代码,再执行一次还是输出kimmking,没有更新的原因是默认没有重新编译代码。
在IDEA里点"Build - recompile "HelloController.java"",或者 "Build - rebuild project",再访问一次即可发现输出了devtools:
% curl http://localhost:8080/helloHello World, devtools!%
实现效果
实现原理
spring-boot-devtools使用了两个类加载器
ClassLoader,一个ClassLoader(base Classloader)加载不会发生更改的类
(依赖的各个jar),另一个ClassLoader(restart ClassLoader)加载会更改的类
(自定义的类,项目src下,编译后到classes文件夹)。
后台启动一个文件监听线程
(File Watcher),监测的目录中的文件发生变动时,原来的restart ClassLoader被丢弃
,将会重新加载新的
restart ClassLoader。
因为文件变动后,第三方jar包不再重新加载,只加载自定义的类
,加载的类比较少,所以重启比较快
。
可以`META-INF/spring-devtools.properties
`在定义哪些jar使用restart Classloader:
restart: exclude: companycommonlibs: "/mycorp-common-[\\w\\d-\\.]+\\.jar" include: projectcommon: "/mycorp-myproj-[\\w\\d-\\.]+\\.jar"
使用限制
1、出于安全考虑,官方非常不建议在生产环境使用。默认只能用于开发模式,组件会判断如果用java -jar之类的方式启动应用,则不启用本功能。不过可以使用 `-Dspring.devtools.restart.enabled=true` 在生产环境来强制开启或者关闭此功能。
2、默认情况下,spring boot的maven打包工具在repackage阶段不会把devtools打进去,如果想要打进去,需要添加`excludeDevtools`参数为false。
3、根据加载原理,每次重新使用classloader加载,导致同一个类,比如cn.kimmking.DemoConfig,前后两个Class是不同的,如果缓存了前一个类或者其实例化的对象,使用后者来做转型或者比较,是不相等的。这一点需要特别注意,这些热加载的类,不能“有状态”。
4、对于页面模版、出错信息、静态资源之类的,spring或其框架本身默认会cache住以提升性能,这个时候,开发期热加载就不会有效果,为了解决这个问题,devtools提供了一系列的默认值来关掉这些cache,详见官方文档[1],如果想要这些默认值不生效,可以把spring.devtools.add-properties
设置为false。
5、如果使用mvn命令来编译和运行springboot,比如执行 mvn spring-boot:run
此时maven-compiler-plugin 插件添加fork为true的配置。否则因为类加载器没有隔离的问题,导致热更新不起作用。使用IDE启动不会有此问题(默认fork)了。
6、如果关闭了spring的shutdown hook,即`SpringApplication.setRegisterShutdownHook(false)`,热更新也不会生效。
7、对于使用`AspectJ`处理的类也不生效。
8、默认对于如下路径的资源不会重新加载(spring boot会保证在线生效),/META-INF/maven
, /META-INF/resources
, /resources
, /static
, /public
, /templates, 可以使用如下方式重载此配置:
`spring.devtools.restart.exclude=static/**,public/**`
9、注意 IDEA的settings-Build,Execution,Deployment-Compiler里有个 build project Automatic选项,备注了不能用于run或debug时,这个没有用。高级设置(advanced settings)里有个allow auto-make 。。。running,这个参数测试也无效。存疑。
高级配置
全局配置
可以配置全局的devtools配置,对于所有的应用都生效,默认在 $HOME/.config/spring-boot
文件夹下的如下文件:
spring-boot-devtools.properties
spring-boot-devtools.yaml
spring-boot-devtools.yml
这个路径可以使用SPRING_DEVTOOLS_HOME
环境变量或者 spring.devtools.home
系统属性来配置。
文件变化轮询间隔
可以通过如下两个参数调整轮询间隔(IDE编译和复制文件需要一定时间)和等待其他文件变动时间。
spring.devtools.restart.poll-interval=2s spring.devtools.restart.quiet-period=1s
主动通知变更
devtools提供了一种主动去通知有变化,去扫描文件变动的方式,就是所谓的触发文件模式。
通过配置:
spring.devtools.restart.trigger-file=.reloadtrigger
然后在src/main/resources下,我们创建一个文件`.reloadtrigger`, 我们可以主动控制这个文件的内容发生变化,此时即可触发devtools进行热更新处理(如果有文件变动)。
远程开发模式使用
devtools可以配合spring boot remote来远程生效。具体参考官方文档引用[1]。
官方文档
转载自:https://juejin.cn/post/7363569085441245184