最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

SpringBoot項目請求不中斷動態(tài)更新代碼的實現(xiàn)

 更新時間:2024年09月30日 11:46:21   作者:不掉頭發(fā)的阿水  
在開發(fā)中,有時候不停機動態(tài)更新代碼熱部署是一項至關(guān)重要的功能,它可以在請求不中斷的情況下下更新代碼,這種方式不僅提高了開發(fā)效率,還能加速測試和調(diào)試過程,本文將詳細(xì)介紹如何在 Spring Boot 項目在Linux系統(tǒng)中實現(xiàn)熱部署,特別關(guān)注優(yōu)雅關(guān)閉功能的實現(xiàn)

1. 代碼概述

我們實現(xiàn)了一個簡單的 Spring Boot 應(yīng)用程序,它可以自動檢測端口是否被占用,并在必要時切換到備用端口,然后再將目標(biāo)端口程序關(guān)閉再將備用端口切換為目標(biāo)端口。具體功能包括:

  • 檢查默認(rèn)端口(8080)是否被占用。
  • 如果被占用,自動切換到備用端口(8086)。
  • 在 Linux 系統(tǒng)下,優(yōu)雅地關(guān)閉占用該端口的進程。
  • 修改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("啟動參數(shù): {}", Arrays.toString(newArgs));
        //去除newArgs的null數(shù)據(jù)
        newArgs = Arrays.stream(newArgs).filter(Objects::nonNull).toArray(String[]::new);
 
        ConfigurableApplicationContext context = SpringApplication.run(MybatisDemoApplication.class, newArgs);
        //判斷是否是linux系統(tǒng),如果是linux系統(tǒng),則嘗試殺死占用8080端口的進程
        System.out.println("是否需要修改端口: "+isNeedChangePort);
        if (isNeedChangePort && isLinuxOS()) {
            changePortAndRestart(context);
        }
    }
 
    /**
     * 如果端口占用,則嘗試殺死占用8080端口的進程,并修改端口并重啟服務(wù)
     *
     * @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("停止舊服務(wù)器.");
            webServer.stop();
        }
        //((TomcatServletWebServerFactory) servletContextInitializer).setPort(DEFAULT_PORT_8080);
        ((TomcatServletWebServerFactory) webServerFactory).setPort(DEFAULT_PORT_8080);
        webServer = webServerFactory.getWebServer(servletContextInitializer);
        webServer.start();
        log.info("新服務(wù)啟動成功.");
    }
 
    /**
     * 殺死占用 8080 端口的進程
     */
    private static void killOldServiceInLinux() {
        try {
            // 查找占用 8080 端口的進程
            String command = "lsof -t -i:" + DEFAULT_PORT_8080;
            log.info("正在執(zhí)行命令: {}", command);
            Process process = Runtime.getRuntime().exec(command);
            BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
            String pid;
            while ((pid = reader.readLine()) != null) {
                // 發(fā)送 SIGINT 信號以優(yōu)雅關(guān)閉
                Runtime.getRuntime().exec("kill -2 " + pid);
                log.info("Killed process: {}", pid);
            }
        } catch (IOException e) {
            log.error("Failed to stop old service", e);
        }
    }
 
    /**
     * 判斷是否是linux系統(tǒng)
     *
     * @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. 主要功能

檢測端口狀態(tài)

通過 PortUtil.isPortInUse() 檢查默認(rèn)端口的使用狀態(tài)。如果端口被占用,修改啟動參數(shù)。

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;
        }
    }
}

修改啟動參數(shù)

當(dāng)發(fā)現(xiàn)端口被占用時,我們動態(tài)調(diào)整啟動參數(shù),以便在啟動時使用新的端口。

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

優(yōu)雅關(guān)閉

在 Linux 系統(tǒng)中,如果檢測到端口被占用,調(diào)用 killOldServiceInLinux() 方法,優(yōu)雅地關(guān)閉占用該端口的進程。這是通過發(fā)送 SIGINT 信號實現(xiàn)的,允許應(yīng)用程序進行清理工作并優(yōu)雅退出。

 /**
     * 殺死占用 8080 端口的進程
     */
    private static void killOldServiceInLinux() {
        try {
            // 查找占用 8080 端口的進程
            String command = "lsof -t -i:" + DEFAULT_PORT_8080;
            log.info("正在執(zhí)行命令: {}", command);
            Process process = Runtime.getRuntime().exec(command);
            BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
            String pid;
            while ((pid = reader.readLine()) != null) {
                // 發(fā)送 SIGINT 信號以優(yōu)雅關(guān)閉
                Runtime.getRuntime().exec("kill -2 " + pid);
                log.info("Killed process: {}", pid);
            }
        } catch (IOException e) {
            log.error("Failed to stop old service", e);
        }
    }

3. 代碼實現(xiàn)

代碼的核心邏輯在 changePortAndRestart() 方法中實現(xiàn),主要步驟包括停止當(dāng)前 Web 服務(wù)器并重啟。

    /**
     * 如果端口占用,則嘗試殺死占用8080端口的進程,并修改端口并重啟服務(wù)
     *
     * @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("停止舊服務(wù)器.");
            webServer.stop();
        }
        //((TomcatServletWebServerFactory) servletContextInitializer).setPort(DEFAULT_PORT_8080);
        ((TomcatServletWebServerFactory) webServerFactory).setPort(DEFAULT_PORT_8080);
        webServer = webServerFactory.getWebServer(servletContextInitializer);
        webServer.start();
        log.info("新服務(wù)啟動成功.");
    }

4. 配置優(yōu)雅關(guān)閉

application.yml 中設(shè)置優(yōu)雅關(guān)閉:

server:
  shutdown: graceful

這個配置允許 Spring Boot 在接收到關(guān)閉請求時,等待當(dāng)前請求完成后再停止服務(wù)。 (因此代碼使用的是kill -2命令)

5. 小結(jié)

通過以上實現(xiàn),我們能夠靈活應(yīng)對端口占用問題,并提升開發(fā)效率。熱部署功能不僅依賴于 Spring Boot 提供的豐富 API,還需要結(jié)合操作系統(tǒng)特性,以確保在生產(chǎn)環(huán)境中的穩(wěn)定性和可用性。

附帶Window關(guān)閉端口程序代碼

(Window關(guān)閉程序后可能得需要sleep一下,不然還會顯示端口占用)

  private static void killOldServiceInWindows() {
            try {
                // 查找占用 8080 端口的進程 ID
                ProcessBuilder 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);
            }
        }

以上就是SpringBoot項目請求不中斷動態(tài)更新代碼的實現(xiàn)的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot不中斷更新代碼的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • java反編譯工具jd-gui-osx?for?mac?M1芯片無法使用的問題及解決

    java反編譯工具jd-gui-osx?for?mac?M1芯片無法使用的問題及解決

    這篇文章主要介紹了java反編譯工具jd-gui-osx?for?mac?M1芯片無法使用的問題及解決方案,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-01-01
  • SpringBoot使用@Cacheable時設(shè)置部分緩存的過期時間方式

    SpringBoot使用@Cacheable時設(shè)置部分緩存的過期時間方式

    這篇文章主要介紹了SpringBoot使用@Cacheable時設(shè)置部分緩存的過期時間方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-12-12
  • 使用dynamic datasource springboot starter實現(xiàn)多數(shù)據(jù)源及源碼分析

    使用dynamic datasource springboot starter實現(xiàn)多數(shù)據(jù)源及源碼分析

    這篇文章主要介紹了使用dynamic-datasource-spring-boot-starter做多數(shù)據(jù)源及源碼分析,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-09-09
  • Spring 處理 HTTP 請求參數(shù)注解的操作方法

    Spring 處理 HTTP 請求參數(shù)注解的操作方法

    這篇文章主要介紹了Spring 處理 HTTP 請求參數(shù)注解的操作方法,本文通過實例代碼給大家介紹的非常詳細(xì),感興趣的朋友參考下吧
    2024-04-04
  • java實現(xiàn)文件讀寫與壓縮實例

    java實現(xiàn)文件讀寫與壓縮實例

    這篇文章主要介紹了java實現(xiàn)文件讀寫與壓縮實例,有助于讀者加深對文件操作的理解,需要的朋友可以參考下
    2014-07-07
  • idea實現(xiàn)類快捷生成接口方法示例

    idea實現(xiàn)類快捷生成接口方法示例

    這篇文章主要介紹了idea實現(xiàn)類快捷生成接口方法示例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-08-08
  • IntelliJ IDEA的build path設(shè)置方法

    IntelliJ IDEA的build path設(shè)置方法

    這篇文章主要介紹了IntelliJ IDEA的build path設(shè)置方法,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-04-04
  • SpringMVC方法返回值多種情況代碼實例

    SpringMVC方法返回值多種情況代碼實例

    這篇文章主要介紹了SpringMVC方法返回值多種情況代碼實例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-09-09
  • Java使用正則表達式驗證手機號和電話號碼的方法

    Java使用正則表達式驗證手機號和電話號碼的方法

    今天小編就為大家分享一篇關(guān)于Java使用正則表達式驗證手機號和電話號碼的方法,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧
    2018-12-12
  • SpringBoot中@FeignClient 注解的作用

    SpringBoot中@FeignClient 注解的作用

    Feign可以幫助我們定義和實現(xiàn)服務(wù)之間的 RESTful 接口,使得服務(wù)之間的調(diào)用更加方便和可靠,本文主要介紹了SpringBoot中@FeignClient 注解的作用,感興趣的可以了解一下
    2024-06-06

最新評論

三河市| 武清区| 大同县| 徐水县| 苏尼特右旗| 抚宁县| 商城县| 马山县| 德兴市| 湄潭县| 五华县| 五华县| 德兴市| 疏附县| 花莲市| 镇巴县| 大理市| 南靖县| 东台市| 彩票| 手机| 巍山| 广水市| 集安市| 兖州市| 西充县| 宜兰市| 临沭县| 洛宁县| 乌兰察布市| 安达市| 辽中县| 柳河县| 双城市| 金湖县| 滨海县| 周口市| 茶陵县| 鸡西市| 聂荣县| 毕节市|