SpringBoot单体服务无感更新启动,动态检测端口号并动态更新

SpringBoot单体服务无感更新启动

java 复制代码
package com.basaltic.warn;

import cn.hutool.core.io.IoUtil;
import lombok.SneakyThrows;
import org.apache.commons.lang3.StringUtils;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.servlet.ServletContextInitializer;
import org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext;
import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import springfox.documentation.oas.annotations.EnableOpenApi;

import java.lang.reflect.Method;
import java.net.ServerSocket;
import java.util.ArrayList;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

@MapperScan("com.basaltic.warn.sys.mapper")
@SpringBootApplication
@EnableTransactionManagement
@EnableOpenApi
@EnableScheduling
@EnableAsync
public class BasalticOneNewApplication {
    
    @SneakyThrows
    public static void main(String[] args) {
        AtomicInteger port = new AtomicInteger(7089);
        SpringApplication app = new SpringApplication(BasalticOneNewApplication.class);
        try {
            app.addInitializers((context) -> {
                String portStr = context.getEnvironment().getProperty("server.port");
                System.out.println("The port is: " + portStr);
                if (StringUtils.isNotBlank(portStr) && StringUtils.isNumeric(portStr)) {
                    port.set(Integer.parseInt(portStr));
                }
            });
            app.run(args);
        } catch (Exception e) {
            String[] newArgs = args.clone();
            boolean needChangePort = false;
            if (isPortInUse(port.get())) {
                newArgs = new String[args.length + 1];
                System.arraycopy(args, 0, newArgs, 0, args.length);
                newArgs[newArgs.length - 1] = "--server.port=7069";
                needChangePort = true;
            }
            ConfigurableApplicationContext run = SpringApplication.run(BasalticOneNewApplication.class, newArgs);
            if (needChangePort) {
                String osName = System.getProperty("os.name");
                while (isPortInUse(port.get())) {
                    if (osName.startsWith("Windows")) {
                        killWinTidByPort(port.get());
                    } else {
                        killLinuxTidByPort(port.get());
                    }
                    System.out.println("已经占用");
                    TimeUnit.SECONDS.sleep(2);
                }
                
                String[] beanNames = run.getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class);
                ServletWebServerFactory webServerFactory = run.getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);
                ((TomcatServletWebServerFactory) webServerFactory).setPort(port.get());
                
                Method method = ServletWebServerApplicationContext.class.getDeclaredMethod("getSelfInitializer");
                method.setAccessible(true);
                ServletContextInitializer invoke = (ServletContextInitializer) method.invoke(run);
                webServerFactory.getWebServer(invoke).start();
                ((ServletWebServerApplicationContext) run).getWebServer().stop();
            }
        }
        
    }
    
    private static boolean isPortInUse(int port) {
        try (ServerSocket serverSocket = new ServerSocket(port)) {
            return false;
        } catch (Exception e) {
            return true;
        }
    }
    
    
    @SneakyThrows
    public static void killLinuxTidByPort(int port) {
        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();
    }
    
    
    @SneakyThrows
    public static void killWinTidByPort(int port) {
        Process process = new ProcessBuilder("cmd.exe", "/c", "taskkill /F /PID " + getWinTidByPort(port)).start();
        process.waitFor(3, TimeUnit.SECONDS);
    }
    
    
    @SneakyThrows
    public static int getWinTidByPort(int port) {
        Process process = new ProcessBuilder("cmd.exe", "/c", "netstat -ano | findstr :" + port).start();
        process.waitFor(3, TimeUnit.SECONDS);
        ArrayList<String> lines = IoUtil.readUtf8Lines(process.getInputStream(), new ArrayList<>());
        for (String line : lines) {
            if (StringUtils.isBlank(line)) {
                continue;
            }
            String tidStr = line.substring(line.trim().lastIndexOf(" ")).trim();
            if (StringUtils.isNotBlank(tidStr) && StringUtils.isNumeric(tidStr)) {
                int tid = Integer.parseInt(tidStr);
                if (tid != 0) {
                    return tid;
                }
            }
        }
        return 0;
    }
}
相关推荐
❥ღ Komo·2 小时前
K8s服务发现与DNS解析全解析
java·开发语言
g***B7383 小时前
Java 工程复杂性的真正来源:从语言设计到现代架构的全链路解析
java·人工智能·架构
期待のcode6 小时前
MyBatisX插件
java·数据库·后端·mybatis·springboot
醇氧8 小时前
【Windows】优雅启动:解析一个 Java 服务的后台启动脚本
java·开发语言·windows
sunxunyong8 小时前
doris运维命令
java·运维·数据库
菜鸟起航ing8 小时前
Spring AI 全方位指南:从基础入门到高级实战
java·人工智能·spring
古城小栈8 小时前
Docker 多阶段构建:Go_Java 镜像瘦身运动
java·docker·golang
MapGIS技术支持9 小时前
MapGIS Objects Java计算一个三维点到平面的距离
java·开发语言·平面·制图·mapgis
Coder_Boy_9 小时前
业务导向型技术日志首日记录(业务中使用的技术栈)
java·驱动开发·微服务
盖世英雄酱5813610 小时前
springboot 项目 从jdk 8 升级到jdk21 会面临哪些问题
java·后端