SpringBoot项目请求不中断动态更新代码

在开发中,有时候不停机动态更新代码热部署是一项至关重要的功能,它可以在请求不中断的情况下下更新代码。这种方式不仅提高了开发效率,还能加速测试和调试过程。本文将详细介绍如何在 Spring Boot 项目在Linux系统中实现热部署,特别关注优雅关闭功能的实现。

1. 代码概述

我们实现了一个简单的 Spring Boot 应用程序,它可以自动检测端口是否被占用,并在必要时切换到备用端口,然后再将目标端口程序关闭再将备用端口切换为目标端口。具体功能包括:

  • 检查默认端口(8080)是否被占用。
  • 如果被占用,自动切换到备用端口(8086)。
  • 在 Linux 系统下,优雅地关闭占用该端口的进程。
  • 修改Tomcat端口并重启容器。

完整代码

import com.lps.utils.PortUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.WebServer;
import org.springframework.boot.web.servlet.ServletContextInitializer;
import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
import org.springframework.context.ConfigurableApplicationContext;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;/*** @author 阿水*/
@SpringBootApplication
@Slf4j
public class MybatisDemoApplication {private static final int DEFAULT_PORT_8080 = 8080;private static final int ALTERNATE_PORT_8086 = 8086;public static void main(String[] args) {boolean isNeedChangePort = PortUtil.isPortInUse(DEFAULT_PORT_8080);String[] newArgs = Arrays.copyOf(args, args.length + 1);if (isNeedChangePort) {log.info("端口 {} 正在使用中, 正在尝试端口切换到 {}.", DEFAULT_PORT_8080, ALTERNATE_PORT_8086);newArgs[newArgs.length - 1] = "--server.port=" + ALTERNATE_PORT_8086;}log.info("启动参数: {}", Arrays.toString(newArgs));//去除newArgs的null数据newArgs = Arrays.stream(newArgs).filter(Objects::nonNull).toArray(String[]::new);ConfigurableApplicationContext context = SpringApplication.run(MybatisDemoApplication.class, newArgs);//判断是否是linux系统,如果是linux系统,则尝试杀死占用8080端口的进程System.out.println("是否需要修改端口: "+isNeedChangePort);if (isNeedChangePort && isLinuxOS()) {changePortAndRestart(context);}}/*** 如果端口占用,则尝试杀死占用8080端口的进程,并修改端口并重启服务** @param context*/private static void changePortAndRestart(ConfigurableApplicationContext context) {log.info("尝试杀死占用 8080 端口的进程.");killOldServiceInLinux();log.info("正在修改端口更改为 {}.", DEFAULT_PORT_8080);ServletWebServerFactory webServerFactory = context.getBean(ServletWebServerFactory.class);ServletContextInitializer servletContextInitializer = context.getBean(ServletContextInitializer.class);WebServer webServer = webServerFactory.getWebServer(servletContextInitializer);if (webServer != null) {log.info("停止旧服务器.");webServer.stop();}//((TomcatServletWebServerFactory) servletContextInitializer).setPort(DEFAULT_PORT_8080);((TomcatServletWebServerFactory) webServerFactory).setPort(DEFAULT_PORT_8080);webServer = webServerFactory.getWebServer(servletContextInitializer);webServer.start();log.info("新服务启动成功.");}/*** 杀死占用 8080 端口的进程*/private static void killOldServiceInLinux() {try {// 查找占用 8080 端口的进程String command = "lsof -t -i:" + DEFAULT_PORT_8080;log.info("正在执行命令: {}", command);Process process = Runtime.getRuntime().exec(command);BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));String pid;while ((pid = reader.readLine()) != null) {// 发送 SIGINT 信号以优雅关闭Runtime.getRuntime().exec("kill -2 " + pid);log.info("Killed process: {}", pid);}} catch (IOException e) {log.error("Failed to stop old service", e);}}/*** 判断是否是linux系统** @return*/private static boolean isLinuxOS() {return System.getProperty("os.name").toLowerCase().contains("linux");}
}

工具类

import java.io.IOException;
import java.net.ServerSocket;/*** @author 阿水*/
public class PortUtil {public static boolean isPortInUse(int port) {try (ServerSocket ignored = new ServerSocket(port)) {// 端口未被占用return false;} catch (IOException e) {// 端口已被占用return true;}}
}

测试效果

2. 主要功能

检测端口状态

通过 PortUtil.isPortInUse() 检查默认端口的使用状态。如果端口被占用,修改启动参数。

import java.io.IOException;
import java.net.ServerSocket;/*** @author 阿水*/
public class PortUtil {public static boolean isPortInUse(int port) {try (ServerSocket ignored = new ServerSocket(port)) {// 端口未被占用return false;} catch (IOException e) {// 端口已被占用return true;}}
}

修改启动参数

当发现端口被占用时,我们动态调整启动参数,以便在启动时使用新的端口。

     if (isNeedChangePort) {log.info("端口 {} 正在使用中, 正在尝试端口切换到 {}.", DEFAULT_PORT_8080, ALTERNATE_PORT_8086);newArgs[newArgs.length - 1] = "--server.port=" + ALTERNATE_PORT_8086;}

优雅关闭

在 Linux 系统中,如果检测到端口被占用,调用 killOldServiceInLinux() 方法,优雅地关闭占用该端口的进程。这是通过发送 SIGINT 信号实现的,允许应用程序进行清理工作并优雅退出。

 
 /*** 杀死占用 8080 端口的进程*/private static void killOldServiceInLinux() {try {// 查找占用 8080 端口的进程String command = "lsof -t -i:" + DEFAULT_PORT_8080;log.info("正在执行命令: {}", command);Process process = Runtime.getRuntime().exec(command);BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));String pid;while ((pid = reader.readLine()) != null) {// 发送 SIGINT 信号以优雅关闭Runtime.getRuntime().exec("kill -2 " + pid);log.info("Killed process: {}", pid);}} catch (IOException e) {log.error("Failed to stop old service", e);}}

3. 代码实现

代码的核心逻辑在 changePortAndRestart() 方法中实现,主要步骤包括停止当前 Web 服务器并重启。

    /*** 如果端口占用,则尝试杀死占用8080端口的进程,并修改端口并重启服务** @param context*/private static void changePortAndRestart(ConfigurableApplicationContext context) {log.info("尝试杀死占用 8080 端口的进程.");killOldServiceInLinux();log.info("正在修改端口更改为 {}.", DEFAULT_PORT_8080);ServletWebServerFactory webServerFactory = context.getBean(ServletWebServerFactory.class);ServletContextInitializer servletContextInitializer = context.getBean(ServletContextInitializer.class);WebServer webServer = webServerFactory.getWebServer(servletContextInitializer);if (webServer != null) {log.info("停止旧服务器.");webServer.stop();}//((TomcatServletWebServerFactory) servletContextInitializer).setPort(DEFAULT_PORT_8080);((TomcatServletWebServerFactory) webServerFactory).setPort(DEFAULT_PORT_8080);webServer = webServerFactory.getWebServer(servletContextInitializer);webServer.start();log.info("新服务启动成功.");}

4. 配置优雅关闭

application.yml 中设置优雅关闭:

server:shutdown: graceful

这个配置允许 Spring Boot 在接收到关闭请求时,等待当前请求完成后再停止服务。 (因此代码使用的是kill -2命令)

5. 小结

通过以上实现,我们能够灵活应对端口占用问题,并提升开发效率。热部署功能不仅依赖于 Spring Boot 提供的丰富 API,还需要结合操作系统特性,以确保在生产环境中的稳定性和可用性。

附带Window关闭端口程序代码

(Window关闭程序后可能得需要sleep一下,不然还会显示端口占用)

  private static void killOldServiceInWindows() {try {// 查找占用 8080 端口的进程 IDProcessBuilder builder = new ProcessBuilder("cmd.exe", "/c", "netstat -ano | findstr :8080");Process process = builder.start();BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));String line;while ((line = reader.readLine()) != null) {String[] parts = line.trim().split("\\s+");if (parts.length > 4) {String pid = parts[parts.length - 1];// 杀死该进程Runtime.getRuntime().exec("taskkill /F /PID " + pid);log.info("Killed process: {}", pid);}}} catch (IOException e) {log.error("Failed to stop old service", e);}}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/438412.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

《业务三板斧:定目标、抓过程、拿结果》读书笔记1

这个书是24年新书,来自阿里系的人的作品,还可以。今天先看前沿部分的精彩部分: 我们在服务企业的过程中,发现了一个常见的管理现象:管理者自 己承担了团队里重要的项目,把风险和压力都集中在自己身上。因 此…

报刊订阅系统小程序的设计

管理员账户功能包括:系统首页,个人中心,用户管理,报刊类型管理,报刊信息管理,报刊订阅管理,订阅发送管理,系统管理 微信端账号功能包括:系统首页,报刊信息&a…

<<迷雾>> 第7章 会变魔术的触发器(1)--连着两个按键开关的逻辑电路 示例电路

info::操作说明 鼠标单击开关切换开合状态 A 能使灯点亮并保持; B 则点亮的灯熄灭. 注: 此处使用的是 按钮开关, 松开鼠标后开关会自己断开, 类似于手机和电脑上的电源按钮 因系统原因, 此类开关与普通开关在外观上并无差别. primary::在线交互操作链接 https://cc.xiaogd.net/…

【Android】获取备案所需的公钥以及签名MD5值

目录 重要前提 获取签名MD5值 获取公钥 重要前提 生成jks文件以及gradle配置应用该文件。具体步骤请参考我这篇文章:【Android】配置Gradle打包apk的环境_generate signed bundle or apk-CSDN博客 你只需要从头看到该文章的配置build.gradle(app&…

HTML流光爱心

文章目录 序号目录1HTML满屏跳动的爱心(可写字)2HTML五彩缤纷的爱心3HTML满屏漂浮爱心4HTML情人节快乐5HTML蓝色爱心射线6HTML跳动的爱心(简易版)7HTML粒子爱心8HTML蓝色动态爱心9HTML跳动的爱心(双心版)1…

回归预测 | Matlab基于POA-SVR鹈鹕算法优化支持向量机的数据多输入单输出回归预测

回归预测 | Matlab基于POA-SVR鹈鹕算法优化支持向量机的数据多输入单输出回归预测 目录 回归预测 | Matlab基于POA-SVR鹈鹕算法优化支持向量机的数据多输入单输出回归预测预测效果基本描述程序设计参考资料 预测效果 基本描述 1.Matlab基于POA-SVR鹈鹕算法优化支持向量机的数据…

检查jar冲突,查找存在相同class的jar

写在前面 本文看下如何查找jar冲突,即查找哪些jar包中存在相同的class。如果是存在相同jar的不同版本,基本一眼就能看出来,然后结合maven的依赖关系将其剔除掉即可,但是当你遇到了有人手动拷贝某些class到jar包中导致冲突的情况时…

wpf实现新用户页面引导

第一步 第二部 部分代码: private void show(int xh, FrameworkElement fe, string con, Visibility vis Visibility.Visible) {Point point fe.TransformToAncestor(Window.GetWindow(fe)).Transform(new Point(0, 0));//获取控件坐标点RectangleGeometry rg new Rectangl…

FP7209: 用于紫外线消毒灯的 升压LED恒流驱动芯片

现在社会对于居家消毒也越发重视起来。而居家消毒除了75%浓度酒精及各类消毒液外,利用紫外线灯给衣物表面、房间消毒也是一种很好的选择。FP7209 定位于低压线性恒流驱动,精度高、外围电路简单、使用方便且可靠性高,更可广泛应用于商业照明系…

鸿蒙harmonyos next flutter通信之BasicMessageChannel获取app版本号

本文将通过BasicMessageChannel获取app版本号,以此来演练BasicMessageChannel用法。 建立channel flutter代码: //建立通道 BasicMessageChannel basicMessageChannel BasicMessageChannel("com.xmg.basicMessageChannel",StringCodec());…

STM32自动下载电路分享及注意事项

文章目录 简介ISP下载启动配置 USB转串口芯片CH340C手动isp下载自动isp下载RTS、DTR电平变化分析注意事项 简介 在嵌入式开发中,使用STM32下载程序,可以通过仿真器下载,也可以通过串口下载。在stm32串口下载时,我们需要手动配置启…

【IPv6】IPv6地址格式及地址分类(组播、单播、任播)整理

IPv6地址格式 IPv6 地址从 IPv4 地址的 32 bits 扩展到 128 bits,IPv6 地址的表示、书写方式也从 IPv4 的点分十进制,修改16进制的冒号分割 IPv4 点分格式(.) 192.168.11.11 IPv6 冒号分割(:) 2408:8459:3032:0000:0000:0000:0001:a9fd IPv6 的规范…

Axure大屏可视化模板在不同领域中的实际应用案例

一、农业领域 案例背景: 智慧农业是当前农业发展的重要趋势,通过物联网、大数据等技术手段,实现农业生产的智能化管理。Axure大屏可视化模板在智慧农业平台的建设中发挥了重要作用。 实际应用: 农田环境监控:通过Axu…

828华为云征文 | 华为云Flexus云服务器X实例搭建企业内部VPN私有隧道,以实现安全远程办公

VPN虚拟专用网络适用于企业内部人员流动频繁和远程办公的情况,出差员工或在家办公的员工利用当地ISP就可以和企业的VPN网关建立私有的隧道连接。 通过拨入当地的ISP进入Internet再连接企业的VPN网关,在用户和VPN网关之间建立一个安全的“隧道”&#xff…

智慧环保大数据平台建设方案

1. 智慧环保现状与挑战 随着环境问题日益严重,环境事件频发,如贵州都匀矿渣污染、云南南盘江水污染等,以及癌症高发率的出现,智慧环保建设显得尤为重要。智慧环保旨在通过技术手段提升环境管理和决策的智能化水平。 2. 宏观环境…

OpenCV Canny()函数

OpenCV Canny()函数被用来检测图像物体的边缘。其算法原理如下: 高斯滤波:使用高斯滤波器平滑图像以减少噪声。高斯滤波器是一种线性滤波器,可以消除图像中的高频噪声,同时保留边缘信息。计算梯度强度和方向:使用Sobe…

MySQL高阶2010-职员招聘人数2

目录 题目 准备数据 分析数据 总结 题目 一家公司想雇佣新员工。公司的工资预算是 $70000 。公司的招聘标准是: 继续雇佣薪水最低的高级职员,直到你不能再雇佣更多的高级职员。用剩下的预算雇佣薪水最低的初级职员。继续以最低的工资雇佣初级职员&…

【华为HCIP实战课程三】动态路由OSPF的NBMA环境建立邻居及排错,网络工程师

一、NBMA环境下的OSPF邻居建立问题 上节我们介绍了NBMA环境下OSPF邻居建立需要手动指定邻居,因为NBMA环境是不支持广播/组播的 上一节AR1的配置: ospf 1 peer 10.1.1.4 //手动指定邻居的接口地址,而不是RID peer 10.1.1.5 area 0.0.0.0 手动指定OSPF邻居后抓包查看OSP…

自动驾驶-轨迹拼接

在进行自动驾驶的规划之前,要确定当前帧轨迹规划的起点,这个起点常被误认为是当前车辆的位置,即每次以车辆的当前位置进行轨迹规划;其实不是这样的,直观上,这会导致本次次规划的轨迹同上次规划的轨迹之间是…

IDEA 使用技巧与插件推荐

目录 前言1. IDEA 使用技巧1.1 快捷键优化与应用1.2 高效调试与日志分析1.3 代码模板与片段的自定义 2. 插件推荐2.1 MyBatisX2.2 Lombok2.3 CheckStyle-IDEA2.4 Key Promoter X2.5 GitToolBox2.6 Rainbow Brackets 3. IDEA 性能优化3.1 内存与堆栈设置3.2 禁用不必要的插件3.3…