You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何通过Web服务启动、停止及重启Spring Boot应用

嘿,这个需求其实挺实用的,我来给你拆解下具体的实现思路和代码示例,都是实际项目里验证过的方案:

核心思路

要通过Web服务控制Spring Boot JAR的启停,本质上就是通过Web接口触发系统命令,同时要做好进程的状态跟踪(比如保存PID)和安全防护,避免非法调用。

具体实现步骤

我推荐写一个轻量的Spring Boot管理服务,专门用来控制目标应用,这样逻辑清晰也容易维护。

1. 准备基础结构

首先创建一个新的Spring Boot项目,只需要引入spring-boot-starter-webspring-boot-starter-security(用来做接口认证)依赖。

2. 进程状态管理:PID文件

启动目标应用时,把它的进程ID(PID)写入一个文件,后续停止/重启时就靠这个PID来定位进程。

3. 编写启停接口

在管理服务里创建一个Controller,实现/start/stop/restart三个接口:

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.File;
import java.io.FileWriter;
import java.nio.file.Files;
import java.nio.file.Paths;

@RestController
public class AppManagerController {

    // 目标JAR的路径
    private static final String APP_JAR_PATH = "/opt/apps/your-app.jar";
    // PID文件路径
    private static final String PID_FILE_PATH = "/opt/apps/app.pid";
    // 日志输出路径
    private static final String LOG_PATH = "/opt/apps/logs/";

    @PostMapping("/start")
    public String startApp() throws Exception {
        // 先检查进程是否已经在运行
        if (isAppRunning()) {
            return "应用已经在运行中";
        }

        // 创建日志目录(如果不存在)
        new File(LOG_PATH).mkdirs();

        // 构建启动命令
        ProcessBuilder pb = new ProcessBuilder("java", "-jar", APP_JAR_PATH);
        pb.directory(new File("/opt/apps/")); // 设置工作目录
        // 重定向输出到日志文件
        pb.redirectOutput(new File(LOG_PATH + "app.out"));
        pb.redirectError(new File(LOG_PATH + "app.err"));

        // 启动进程
        Process process = pb.start();
        // 保存PID到文件
        try (FileWriter writer = new FileWriter(PID_FILE_PATH)) {
            writer.write(String.valueOf(process.pid()));
        }

        return "应用启动成功,PID: " + process.pid();
    }

    @PostMapping("/stop")
    public String stopApp() throws Exception {
        if (!isAppRunning()) {
            return "应用当前未运行";
        }

        // 读取PID
        String pid = new String(Files.readAllBytes(Paths.get(PID_FILE_PATH)));
        ProcessBuilder pb;

        // 兼容Windows和Linux
        String os = System.getProperty("os.name").toLowerCase();
        if (os.contains("win")) {
            pb = new ProcessBuilder("taskkill", "/PID", pid, "/F");
        } else {
            pb = new ProcessBuilder("kill", pid);
        }

        // 执行停止命令
        Process process = pb.start();
        int exitCode = process.waitFor();

        // 删除PID文件
        new File(PID_FILE_PATH).delete();

        return exitCode == 0 ? "应用停止成功" : "应用停止失败";
    }

    @PostMapping("/restart")
    public String restartApp() throws Exception {
        stopApp();
        // 等待2秒确保进程完全停止
        Thread.sleep(2000);
        return startApp();
    }

    // 检查应用是否在运行
    private boolean isAppRunning() {
        File pidFile = new File(PID_FILE_PATH);
        if (!pidFile.exists()) {
            return false;
        }

        try {
            String pid = new String(Files.readAllBytes(Paths.get(PID_FILE_PATH)));
            String os = System.getProperty("os.name").toLowerCase();
            ProcessBuilder pb;

            if (os.contains("win")) {
                pb = new ProcessBuilder("tasklist", "/FI", "PID eq " + pid);
            } else {
                pb = new ProcessBuilder("ps", "-p", pid);
            }

            Process process = pb.start();
            int exitCode = process.waitFor();
            // Linux下ps -p pid存在返回0,否则1;Windows下tasklist同理
            return exitCode == 0;
        } catch (Exception e) {
            return false;
        }
    }
}

4. 接口安全防护

绝对不能让这些接口随便被访问!我们用Spring Security加个简单的HTTP Basic认证:

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            // 禁用CSRF(如果是内部调用可以禁用,外部调用建议保留并配置)
            .csrf().disable()
            .authorizeRequests()
            // 对三个管理接口要求认证
            .antMatchers("/start", "/stop", "/restart").authenticated()
            // 其他接口允许匿名访问
            .anyRequest().permitAll()
            .and()
            // 使用HTTP Basic认证
            .httpBasic();
    }

    @Override
    protected void configure(org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder auth) throws Exception {
        // 内存中配置用户名密码(生产环境建议用数据库存储加密后的密码)
        auth.inMemoryAuthentication()
            .withUser("admin")
            .password("{noop}admin@123") // {noop}表示不加密,生产环境换成BCrypt加密后的密码
            .roles("ADMIN");
    }
}
关键注意事项
  • 权限问题:管理服务的运行用户需要有执行javakill(或taskkill)命令的权限,同时要有读写PID文件和日志目录的权限。
  • 跨平台兼容:代码里已经做了Windows和Linux的命令兼容,部署时注意测试。
  • 日志管理:一定要把目标应用的输出重定向到日志文件,不然启动后输出会占用管理服务的控制台资源。
  • 进程检查:启动前先检查进程是否存活,避免重复启动导致端口冲突。
  • 生产环境优化:生产环境不要用内存存储用户名密码,换成数据库+BCrypt加密;可以增加接口限流,防止恶意调用。

内容的提问来源于stack exchange,提问作者Belgacem

火山引擎 最新活动