前言
你是否因为每次改动代码还需要启停项目而烦恼?你是否因为每次测试环境更新代码都需要重新打包而烦恼?今天它来了,它就是我们今天的主题Hot-Swapping(热拔插)。
Hot Swapping
我们先看下官方介绍(大家可以根据自己依赖的springboot版本自行下载对应的文档,因为我写这篇文章时用的是2.2.7.RELEASE,这里就拿这个版本的文档进行讲解)。
7.5. Hot Swapping
Since Spring Boot applications are just plain Java applications, JVM hot-swapping should work out of the box. JVM hot swapping is somewhat limited with the bytecode that it can replace. For a more complete solution, JRebel can be used.
Thespring-boot-devtoolsmodule also includes support for quick application restarts. See the Developer Tools section later in this chapter and the Hot swapping “How-to” for details.
通过上面官方的描述我们可以知道springboot 有个模块叫spring-boot-devtools可以支持应用的快速重启。
如何启用spring-boot-devtools
我本地是Maven项目,开发工具是IDEA,这里就以这两个作为基准进行讲解
1.添加maven依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional> <!--optional表示该依赖只在本项目传递,若其他项目依赖本项目,该依赖无效-->
</dependency>
</dependencies>
我们来看下官方的注意事项
Developer tools are automatically disabled when running a fully packaged application. If your application is launched from java -jar or if it is started from a special classloader, then it is considered a “production application”. If that does not apply to you (i.e. if you run your application from a container), consider excluding devtools or set the -Dspring.devtools.restart.enabled=false system property.
Flagging the dependency as optional in Maven or using a custom developmentOnly configuration in Gradle (as shown above) is a best practice that prevents devtools from being transitively applied to other modules that use your project.
1)首先SpringBoot-devtools 当运行全包形式的程序时, 就是通过 java -jar xxxx.jar来启动项目时,会自动禁用开发者工具。 还有一种情况就是在container下面,我们需要通过系统参数来禁用: -Dspring.devtools.restart.enabled=false。
2)在Maven中将依赖项标记为optional或在Gradle中使用自定义developmentOnly配置,可以防止将devtools依赖传递到其他模块。
2.默认属性
默认情况下SpringBoot-devtools会禁用缓存选项,我们无须通过application.properties手动去禁用一些模板引擎(比如thymeleaf等),官方文档指出禁用清单如下:
static {
Map<String, Object> properties = new HashMap<>();
properties.put("spring.thymeleaf.cache", "false");
properties.put("spring.freemarker.cache", "false");
properties.put("spring.groovy.template.cache", "false");
properties.put("spring.mustache.cache", "false");
properties.put("server.servlet.session.persistent", "true");
properties.put("spring.h2.console.enabled", "true");
properties.put("spring.resources.cache.period", "0");
properties.put("spring.resources.chain.cache", "false");
properties.put("spring.template.provider.cache", "false");
properties.put("spring.mvc.log-resolved-exception", "true");
properties.put("server.error.include-stacktrace", "ALWAYS");
properties.put("server.servlet.jsp.init-parameters.development", "true");
properties.put("spring.reactor.debug", "true");
PROPERTIES = Collections.unmodifiableMap(properties);
}
如果不想使用上面的默认配置,可以在application.properties中通过spring.devtools.add-properties = false来禁用。
3.验证
1)简易的controller
package com.sunyard.eshop.controller;
import com.sunyard.eshop.entity.Order;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class OrderController {
@RequestMapping("/order/{id}")
public Order getOrder(@PathVariable("id") String orderId){
Order order = new Order();
order.setOrderId(orderId);
order.setPhoneNo("139xxxxxx");
order.setProductName("IPHONE");
return order;
}
}
启动服务,页面输入http://localhost:8080/order/3,得到结果

这时候我们不要启停服务,直接修改代码
package com.sunyard.eshop.controller;
import com.sunyard.eshop.entity.Order;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class OrderController {
@RequestMapping("/order/{id}")
public Order getOrder(@PathVariable("id") String orderId){
Order order = new Order();
order.setOrderId(orderId);
order.setPhoneNo("158xxxxxx");
order.setProductName("HUAWEI");
return order;
}
}
然后build -> build project (快捷键 ctrl + F9),控制台会出现如下信息

这时候我们再次访问http://localhost:8080/order/3,

此时productName变成了HUAWEI,phoneNo变成了158xxxxxx,这样我们就完成了本地代码的热加载,简单吧!
如何远程启动SpringBoot-devtools
有时候我们本地改动一些代码之后,不想重新打包到测试环境服务器上,这时候我们可以采用SpringBoot-devtools的远程热拔插。
1.修改pom文件
pom文件新增 <excludeDevtools>false</excludeDevtools>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludeDevtools>false</excludeDevtools> <!--新增-->
</configuration>
</plugin>
2.修改application.properties
spring.devtools.remote.secret=mysecret
mycecret是自定义密码,client和remote server之间同步代码时就使用这个密码进行校验
3.修改本地服务启动配置
指定Main Class为org.springframework.boot.devtools.RemoteSpringApplication
指定Program arguments为http://127.0.0.1:8080 即远端服务的地址(自行更换地址)

4.验证
1)先将服务打包,并启动,用来模拟远程端(我是用一台电脑做测试,远程端口号是8080),这时候远程端会有这样一句话输出,代表我们远程端的配置生效了

2)通过IDEA启动本地服务(我是一台电脑测试,这里本地代码启动端口是8081),这时候控制台有如下的输出,代表已经连接上了远程服务。

3)访问远程服务 http://localhost:8080/order/4

4)修改本地服务代码,并通过build -> build project (crtl + F9)重新加载代码
package com.sunyard.eshop.controller;
import com.sunyard.eshop.entity.Order;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class OrderController {
@RequestMapping("/order/{id}")
public Order getOrder(@PathVariable("id") String orderId){
Order order = new Order();
order.setOrderId(orderId);
order.setPhoneNo("199xxxxxx");
order.setProductName("XIAOMI");
return order;
}
}
这时候发现服务端自动重启了一次

5)再次访问远程服务 http://localhost:8080/order/4,通过截图可以看到,远程服务已经同步了本地的代码,无须我们再次打包上传到远程服务了。

结束语
本次springboot热拔插的分享就到此结束了,以后我会不定时更新springboot使用的一些小技巧,欢迎大家一起学习 ^ ^