SpringBoot - 动态端口切换黑魔法
文章目录
- 关键技术点
- 核心原理
- Code

关键技术点
利用 Spring Boot 内嵌 Servlet 容器 和 动态端口切换 的方式实现平滑更新的方案,关键技术点如下:
- Servlet 容器重新绑定端口:Spring Boot 使用
ServletWebServerFactory动态设置新端口。 - 零停机切换:通过先启动备用服务、释放主端口,再切换新服务到主端口,实现服务的无缝切换。
- 端口检测和进程终止:使用
ServerSocket和系统命令来检测和操作端口。
这种设计允许服务在不完全停止的情况下切换到更新的版本,从而极大地缩短了不可用时间,实现了接近于零停机的效果。
核心原理
-
内嵌 Tomcat 容器动态启动:
- 使用
TomcatServletWebServerFactory实现容器的动态创建和启动。 - 动态绑定
DispatcherServlet通过ServletContextInitializer集合完成 Servlet 注册。
- 使用
-
端口检查和动态切换:
- 通过
ServerSocket判断端口是否占用。 - 如果占用,则先用备用端口启动新服务,再通过关闭老服务释放主端口,最后切换新服务到主端口。
- 通过
-
运行时自动处理:
- 利用
Runtime.exec执行系统命令,释放端口并终止旧进程。 - 在极短时间内完成新旧服务切换,避免长时间的停机。
- 利用
Code
package com.artisan;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.ServletContextInitializerBeans;
import org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext;
import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
import org.springframework.context.ConfigurableApplicationContext;import java.io.IOException;
import java.net.ServerSocket;
import java.util.Collections;@SpringBootApplication()
public class BootMainApplication {public static void main(String[] args) {// 默认端口设置int defaultPort = 8080;// 备选端口设置int alternativePort = 9090;// 检查默认端口是否已被占用boolean isPortOccupied = isPortInUse(defaultPort);// 动态端口分配int portToUse = isPortOccupied ? alternativePort : defaultPort;// 创建Spring Boot应用实例SpringApplication app = new SpringApplication(WebMainApplication2.class);// 设置端口配置app.setDefaultProperties(Collections.singletonMap("server.port", portToUse));// 运行应用并获取上下文ConfigurableApplicationContext context = app.run(args);// 如果默认端口被占用,则尝试切换回默认端口if (isPortOccupied) {switchToDefaultPort(context, defaultPort, portToUse);}}/*** 切换到默认端口** 当默认端口被其他进程占用时,此方法尝试释放该端口,并启动一个新的Web服务器实例绑定到默认端口* 同时,它会停止当前的Web服务器实例** @param context 当前应用上下文,用于访问Web服务器工厂和停止当前Web服务器* @param defaultPort 默认端口号,希望切换到的目标端口* @param currentPort 当前Web服务器正在使用的端口号*/private static void switchToDefaultPort(ConfigurableApplicationContext context, int defaultPort, int currentPort) {try {// 释放默认端口terminateProcessUsingPort(defaultPort);// 等待端口释放while (isPortInUse(defaultPort)) {Thread.sleep(100);}// 启动新容器绑定默认端口ServletWebServerFactory webServerFactory = getWebServerFactory(context);((TomcatServletWebServerFactory) webServerFactory).setPort(defaultPort);WebServer newServer = webServerFactory.getWebServer(getServletContextInitializers(context));newServer.start();// 停止当前容器((ServletWebServerApplicationContext) context).getWebServer().stop();} catch (Exception e) {e.printStackTrace();}}/*** 检查指定的端口是否正在使用** @param port 要检查的端口号* @return 如果端口正在使用,则返回true;否则返回false*/private static boolean isPortInUse(int port) {try (ServerSocket serverSocket = new ServerSocket(port)) {// 如果能够成功创建ServerSocket实例,说明端口可用,返回falsereturn false;} catch (IOException e) {// 如果创建ServerSocket实例时抛出IOException,说明端口已被占用,返回truereturn true;}}/*** 终止使用指定端口的进程** @param port 需要释放的端口号* @throws IOException 如果执行命令发生错误* @throws InterruptedException 如果线程被中断*/private static void terminateProcessUsingPort(int port) throws IOException, InterruptedException {// 构建终止使用指定端口的进程的命令String command = String.format("lsof -i :%d | grep LISTEN | awk '{print $2}' | xargs kill -9", port);// 执行命令并等待命令执行完成Runtime.getRuntime().exec(new String[]{"sh", "-c", command}).waitFor();}/*** 获取ServletContextInitializer实例* 该方法用于将Spring应用上下文中的所有ServletContextInitializerBeans实例* 转换为ServletContextInitializer接口的实现,以便在应用启动时初始化ServletContext** @param context Spring的应用上下文,用于获取BeanFactory* @return 返回一个实现了ServletContextInitializer接口的实例*/private static ServletContextInitializer getServletContextInitializers(ConfigurableApplicationContext context) {// 使用ApplicationContext中的BeanFactory创建ServletContextInitializerBeans实例// 这里将ServletContextInitializerBeans作为ServletContextInitializer的实现类返回// ServletContextInitializerBeans将会负责收集应用上下文中所有ServletContextInitializer的实现// 并在应用启动时依次调用它们的onStartup方法来初始化ServletContextreturn (ServletContextInitializer) new ServletContextInitializerBeans(context.getBeanFactory());}/*** 获取Servlet Web服务器工厂** @param context 可配置的应用上下文,用于获取Bean工厂* @return ServletWebServerFactory实例,用于配置和创建Web服务器*/private static ServletWebServerFactory getWebServerFactory(ConfigurableApplicationContext context) {// 从应用上下文中获取Bean工厂,并从中获取ServletWebServerFactory实例return context.getBeanFactory().getBean(ServletWebServerFactory.class);}
}
测试
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@RestController()
@RequestMapping("port/")
public class TestPortController {@GetMapping("test")public String test() {return "artisan-old";}
}
启动后,访问 http://localhost:8080/port/test
修改TestPortController 的返回值, 打个jar包, 启动新的jar包,
重新访问 http://localhost:8080/port/test ,观察返回结果是否是修改后的返回值
参考:https://mp.weixin.qq.com/s/_rt1NP_LPfzatb0EYXry9Q

相关文章:
SpringBoot - 动态端口切换黑魔法
文章目录 关键技术点核心原理Code 关键技术点 利用 Spring Boot 内嵌 Servlet 容器 和 动态端口切换 的方式实现平滑更新的方案,关键技术点如下: Servlet 容器重新绑定端口:Spring Boot 使用 ServletWebServerFactory 动态设置新端口。零停…...
Java爬虫技术:挖掘淘宝数据的利器
在当今大数据时代,网络爬虫技术已经成为获取网络数据的重要手段。Java作为一种强大且灵活的编程语言,非常适合开发复杂的网络爬虫系统。本文将详细介绍Java爬虫能够爬取的淘宝数据类型,并提供具体的代码示例,帮助您快速入门并掌握…...
Chromium for Android 浏览器的编译和安装
Chromium for Android 浏览器的编译和安装 Chromium for Android 浏览器的编译和安装环境要求和配置Chromium for Android源码下载安装 depot_tools获取代码转换现有的Linux检出安装额外的构建依赖运行钩子 Chromium for Android源码编译设置编译环境 编译 ChromiumChromium fo…...
实景视频与模型叠加融合?
[视频GIS系列]无人机视频与与实景模型进行实时融合_无人机视频融合-CSDN博客文章浏览阅读1.5k次,点赞28次,收藏14次。将无人机视频与实景模型进行实时融合是一个涉及多个技术领域的复杂过程,主要包括无人机视频采集、实景模型构建、视频与模型…...
Scala的隐式类
package hfd //隐式类 //任务:给之前的BaseUser添加新的功能,但是不要直接去改代码 //思路:把BaseUser通过隐式转换,改成一个新类型,而这个新类型中有这新的方法 //implicit class一个隐式转换函数类 //作用࿱…...
常见软件设计模式介绍:三层架构、MVC、SSM、EDD、DDD
三层架构(View Service Dao) 三层架构是指:视图层 view(表现层),服务层 service(业务逻辑层),持久层 Dao(数据访问层) 表现层:直接跟前…...
Springboot技术栈常见问题及搭建步骤
一. SpringBoot介绍 1.1. 引言 为了使用SSM框架去开发, 准备SSM框架的模板配置 为了使Spring整合第三方框架, 单独的去编写xml文件 导致SSM项目后期xml文件特别多, 维护xml文件的成本是很高的 SSM工程部署也是很麻烦, 依赖第三方的容器 SSM开发方式很是笨重 1.2 SpringBoot …...
session 共享服务器
1.安装 kryo-3.0.3.jar asm-5.2.jar objenesis-2.6.jar reflectasm-1.11.9.jar minlog-1.3.1.jar kryo-serializers-0.45.jar msm-kryo-serializer-2.3.2.jar memcached-session-manager-tc9-2.3.2.jar spymemcached-2.12.3.jar memcached-session-manager-2.3.2.jar …...
vue2:v-for实现的el-radio-group选中时显示角标,并自定义选中按钮的字体颜色和背景色
项目中需要实现一组预定义查询,每一个查询按钮在选中时右上角显示一个角标,展示当前查询返回的数据条目。 1、text-color="#3785FF" fill="#E6EAF1" 处理选中时的字体颜色和背景色,如上图,分别为蓝色和浅灰色。 2、badge中:value="selectedRadio…...
【Linux】-学习笔记10
第八章、Linux下的火墙管理及优化 1.什么是防火墙 从功能角度来讲 防火墙是位于内部网和外部网之间的屏障,它按照系统管理员预先定义好的规则来控制数据包的进出 从功能实现角度来讲 火墙是系统内核上的一个模块netfilter(数据包过滤机制) …...
鸿蒙NEXT开发案例:九宫格随机
【引言】 在鸿蒙NEXT开发中,九宫格抽奖是一个常见且有趣的应用场景。通过九宫格抽奖,用户可以随机获得不同奖品,增加互动性和趣味性。本文将介绍如何使用鸿蒙开发框架实现九宫格抽奖功能,并通过代码解析展示实现细节。 【环境准…...
深度解析:RTC电路上的32.768KHz时钟的频偏及测试
1、什么是RTC RTC是Real-Time Clock(实时时钟)的缩写,通常在电子产品中,是用时钟电路(外部采用时钟芯片,比如AiP8563)或时钟模块(SOC内部包含了时钟模块,只需要外接32.768KHz晶振)来…...
Scala的泛型
需求:定义一个名为getMiddleEle 的方法用它来获取当前的列表的中间位置的值中间位置的下标 长度/2目标:getMiddleEle(List(1,2,3,4,5)) > 5/2 2 > 下标为2的元素是:3 getMiddleEle(List(1,2,3,4)) > 4/2 2 > 下标为2的元素是:3格式如下: 定义一个函数的格式:def…...
OpenGL ES详解——glUniform1i方法是否能用于设置纹理单元
glUniform1i 方法确实可以用于设置纹理单元(texture unit)。在OpenGL中,纹理单元是图形硬件的一部分,它允许你同时绑定多个纹理,并在着色器程序中通过uniform变量来选择使用哪个纹理。 通常,纹理单元通过整…...
探索 Janus-1.3B:一个统一的 Any-to-Any 多模态理解与生成模型
随着多模态技术的不断发展,越来越多的模型被提出以解决跨文本与图像等多种数据类型的任务。Janus-1.3B 是由 DeepSeek 推出的一个革命性的模型,它通过解耦视觉编码并采用统一的 Transformer 架构,带来了一个高度灵活的 any-to-any 多模态框架…...
论文信息搜集
系列博客目录 文章目录 系列博客目录1.秩典型相关分析及其在视觉搜索重排序中的应用《Rank canonical correlation analysis and its application in visual search reranking》2.利用边信息的规范秩估计在多维谐波恢复中的应用《Canonical Rank Estimation Using Side Informa…...
实操给自助触摸一体机接入大模型语音交互
本文以CSK6 大模型开发板串口触摸屏为例,实操讲解触摸一体机怎样快速增加大模型语音交互功能,使用户能够通过语音在一体机上查询信息、获取智能回答及实现更多互动功能等。 在本文方案中通过CSK6大模型语音开发板采集用户语音,将语音数据传输…...
图表的放大和刷新功能
正常图表渲染显示: // 漏斗ading动画 let myChartone; // 获取配置项 let optionone; // 获取漏斗的数据 let order; let pay_order; let pay_order_num; let pay_order_num_num; let optiones; // 漏斗渲染 function polt(data) {// 从名为data的对象中获取ordata属…...
SQLServer利用QQ邮箱做SMTP服务器发邮件
环境 Microsoft SQL Server 2019 (RTM) - 15.0.2000.5 (X64) SQL Server Management Studio 15.0.18384.0 SQL Server 管理对象 (SMO) 16.100.46367.54 Microsoft .NET Framework 4.0.30319.42000 操作系统 Windows Server2019 ———————————————— 前言…...
flutter 多文本,其中文本下划线往下移动
变态需求 flutter中再满足多行文本,文本内有多个样式,并且多个样式可触发事件的情况,将其中的一部分文本的下划线往下移 方式一: 实现 使用RichText组件,主要是看中里面的WidgetSpan可以穿child为一个widget 实现源…...
OpenLayers 可视化之热力图
注:当前使用的是 ol 5.3.0 版本,天地图使用的key请到天地图官网申请,并替换为自己的key 热力图(Heatmap)又叫热点图,是一种通过特殊高亮显示事物密度分布、变化趋势的数据可视化技术。采用颜色的深浅来显示…...
连锁超市冷库节能解决方案:如何实现超市降本增效
在连锁超市冷库运营中,高能耗、设备损耗快、人工管理低效等问题长期困扰企业。御控冷库节能解决方案通过智能控制化霜、按需化霜、实时监控、故障诊断、自动预警、远程控制开关六大核心技术,实现年省电费15%-60%,且不改动原有装备、安装快捷、…...
论文浅尝 | 基于判别指令微调生成式大语言模型的知识图谱补全方法(ISWC2024)
笔记整理:刘治强,浙江大学硕士生,研究方向为知识图谱表示学习,大语言模型 论文链接:http://arxiv.org/abs/2407.16127 发表会议:ISWC 2024 1. 动机 传统的知识图谱补全(KGC)模型通过…...
WEB3全栈开发——面试专业技能点P2智能合约开发(Solidity)
一、Solidity合约开发 下面是 Solidity 合约开发 的概念、代码示例及讲解,适合用作学习或写简历项目背景说明。 🧠 一、概念简介:Solidity 合约开发 Solidity 是一种专门为 以太坊(Ethereum)平台编写智能合约的高级编…...
实现弹窗随键盘上移居中
实现弹窗随键盘上移的核心思路 在Android中,可以通过监听键盘的显示和隐藏事件,动态调整弹窗的位置。关键点在于获取键盘高度,并计算剩余屏幕空间以重新定位弹窗。 // 在Activity或Fragment中设置键盘监听 val rootView findViewById<V…...
python执行测试用例,allure报乱码且未成功生成报告
allure执行测试用例时显示乱码:‘allure’ �����ڲ����ⲿ���Ҳ���ǿ�&am…...
html-<abbr> 缩写或首字母缩略词
定义与作用 <abbr> 标签用于表示缩写或首字母缩略词,它可以帮助用户更好地理解缩写的含义,尤其是对于那些不熟悉该缩写的用户。 title 属性的内容提供了缩写的详细说明。当用户将鼠标悬停在缩写上时,会显示一个提示框。 示例&#x…...
智能AI电话机器人系统的识别能力现状与发展水平
一、引言 随着人工智能技术的飞速发展,AI电话机器人系统已经从简单的自动应答工具演变为具备复杂交互能力的智能助手。这类系统结合了语音识别、自然语言处理、情感计算和机器学习等多项前沿技术,在客户服务、营销推广、信息查询等领域发挥着越来越重要…...
Linux 内存管理实战精讲:核心原理与面试常考点全解析
Linux 内存管理实战精讲:核心原理与面试常考点全解析 Linux 内核内存管理是系统设计中最复杂但也最核心的模块之一。它不仅支撑着虚拟内存机制、物理内存分配、进程隔离与资源复用,还直接决定系统运行的性能与稳定性。无论你是嵌入式开发者、内核调试工…...
C#中的CLR属性、依赖属性与附加属性
CLR属性的主要特征 封装性: 隐藏字段的实现细节 提供对字段的受控访问 访问控制: 可单独设置get/set访问器的可见性 可创建只读或只写属性 计算属性: 可以在getter中执行计算逻辑 不需要直接对应一个字段 验证逻辑: 可以…...
