当前位置: 代码网 > it编程>编程语言>Java > SpringBoot项目请求不中断动态更新代码的实现

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

2024年09月30日 Java 我要评论
1. 代码概述我们实现了一个简单的 spring boot 应用程序,它可以自动检测端口是否被占用,并在必要时切换到备用端口,然后再将目标端口程序关闭再将备用端口切换为目标端口。具体功能包括:检查默认

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 端口的进程 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项目请求不中断动态更新代码的实现的详细内容,更多关于springboot不中断更新代码的资料请关注代码网其它相关文章!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com