如何通过Web服务启动、停止及重启Spring Boot应用
嘿,这个需求其实挺实用的,我来给你拆解下具体的实现思路和代码示例,都是实际项目里验证过的方案:
核心思路
要通过Web服务控制Spring Boot JAR的启停,本质上就是通过Web接口触发系统命令,同时要做好进程的状态跟踪(比如保存PID)和安全防护,避免非法调用。
具体实现步骤
我推荐写一个轻量的Spring Boot管理服务,专门用来控制目标应用,这样逻辑清晰也容易维护。
1. 准备基础结构
首先创建一个新的Spring Boot项目,只需要引入spring-boot-starter-web和spring-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"); } }
关键注意事项
- 权限问题:管理服务的运行用户需要有执行
java、kill(或taskkill)命令的权限,同时要有读写PID文件和日志目录的权限。 - 跨平台兼容:代码里已经做了Windows和Linux的命令兼容,部署时注意测试。
- 日志管理:一定要把目标应用的输出重定向到日志文件,不然启动后输出会占用管理服务的控制台资源。
- 进程检查:启动前先检查进程是否存活,避免重复启动导致端口冲突。
- 生产环境优化:生产环境不要用内存存储用户名密码,换成数据库+BCrypt加密;可以增加接口限流,防止恶意调用。
内容的提问来源于stack exchange,提问作者Belgacem




