# spring-boot-runner **Repository Path**: boluoshu/spring-boot-runner ## Basic Information - **Project Name**: spring-boot-runner - **Description**: vscode spring boot启动管理插件 - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-09-17 - **Last Updated**: 2026-10-27 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Spring Boot Runner(免插件)— VS Code 扩展 > 🌐 **语言 / Language:** **中文** | [English](#english) 一个在命令行直接运行**未打包**的 Spring Boot(Maven)项目的 VS Code 扩展:通过 `java -cp` 启动,既不需要 `spring-boot-maven-plugin`,也不依赖官方的 VS Code Spring Boot 插件。 它是 `scripts/run-spring-boot.mjs` 的一层薄 UI —— 一个核心脚本、一个入口: - **VS Code 扩展** → 人在编辑器里点一下命令。 运行器是纯 Node(ESM)实现,因此**无需安装 Python** —— 只需要 PATH 上有 Maven 和一个 JDK。 ## 源码仓库 Gitee:`https://gitee.com/boluoshu/spring-boot-runner`(分支 `master`) ```bash git clone https://gitee.com/boluoshu/spring-boot-runner.git ``` ## 为什么要做这个 Spring Boot Dashboard「运行」应用的方式是:解析出 `@SpringBootApplication` 主类 + 类路径, 然后调用 `java -cp `。本扩展用纯命令行工具复刻了这一过程 (用 `maven-dependency-plugin` 取代 IDE 的 JDT 类路径解析器),全程不需要 `spring-boot-maven-plugin`。超长的类路径写入临时目录下按项目区分的 `@argfile`, 启动命令因此保持很短,也绕开了 Windows `CreateProcess` 的命令行长度限制。 ## 命令 | 命令 | 作用 | | --- | --- | | `Spring Boot Runner: 运行项目` | 用 `.vscode/launch.json` 里的一条运行配置启动。有多条时会询问用哪条;一条都没有则自动探测。 | | `Spring Boot Runner: 运行测试…` | 跑测试并直接退出(不启动 java):`mvn test` / `gradle test`,可输入过滤串(如 `com.foo.*Test`),会记住上次输入。 | | `Spring Boot Runner: 调试运行(JDWP)` | 同上,但以 JDWP 启动(`suspend` 按配置),并自动附加 VS Code 的 Java 调试器,断点立即可用。 | | `Spring Boot Runner: 附加自定义参数运行` | 先提示输入额外参数(如 `--profiles dev --port 8080`),再运行。含空格的路径要加引号;这里的 `--java-home` 只对本次运行生效。 | | `Spring Boot Runner: 设置 JDK(JAVA_HOME)…` | 提示输入 JDK 目录(会校验 `bin/java(.exe)`),存入选定的运行配置。输入为空即清除。 | | `Spring Boot Runner: 停止` | 停止正在运行/调试的应用:分离调试器、通过 PID 文件杀掉整个 Java 进程树(`taskkill /PID … /T /F`),并兜底向终端发送 Ctrl-C。 | 输出在一个可复用的集成终端里,终端名为 **Spring Boot Runner**。 ## 运行/调试配置 —— `.vscode/launch.json` 运行/调试配置保存在项目的 **`.vscode/launch.json`** 中,使用本扩展自己的调试器类型 `spring-boot-runner`。它们就是普通的调试配置,因此同一份文件同时驱动三个入口: - **运行和调试**视图(侧边栏下拉框); - **F5**(带调试运行)与 **Ctrl+F5**(不带调试运行); - 本扩展贡献的 **运行/调试配置…** 面板(类 IDEA 的表单)。 `Ctrl+Shift+P → Spring Boot Runner: 运行/调试配置…` 打开面板:左侧是配置树,右侧是选中配置的 表单。运行参数没有单独的手写配置文件 —— 这个面板*就是* `launch.json` 的编辑器。 ```jsonc { "version": "0.2.0", "configurations": [ { "type": "spring-boot-runner", "request": "launch", "name": "应用程序 1", "module": "backend/bonlink-agent", // 留空 = 自动探测 "mainClass": "com.bonlink.PlatformApplication", // 留空 = 自动探测 "profiles": "dev", "port": "8081", "vmArgs": "-Xmx1g -Denv=dev", "env": "SPRING_DATASOURCE_URL=jdbc:mysql://localhost/db", "javaHome": "D:\\soft\\Java\\jdk-21.0.8+9", "skipTests": true }, { "type": "spring-boot-runner", "request": "attach", // 不构建、不启动,只附加 "name": "远程 JVM 调试 1", "hostName": "10.0.0.5", "port": 5005 } ] } ``` `request: "launch"` 字段: | 字段 | 默认值 | 含义 | | --- | --- | --- | | `module` | `""` | 构建模块目录名(Maven 的 pom.xml 或 Gradle 的 build.gradle(.kts),留空自动探测)。 | | `mainClass` | `""` | 全限定主类名(留空自动探测 @SpringBootApplication;面板里是带候选的下拉框,可直接选)。 | | `programArgs` | `""` | 程序实参,**每行一条**;不换行时仍按空格分隔(兼容旧写法)。 | | `vmArgs` | `""` | 额外的 JVM 参数,**每行一条**;不换行时仍按空格分隔(兼容旧写法)。 | | `extraClasspath` | `[]` | **额外的 classpath 条目,可以写多个。** JSON 数组(在 `launch.json` 里一行一条)或单个字符串、用换行/`;` 分隔。追加在 Maven 解析出的类路径**之后**,因此只做补充、不打乱既有顺序,可用于补上缺失的 jar 或另一个模块的 `target/classes`。相对路径按项目根解析,支持 `${workspaceFolder}` 等变量。不参与构建缓存 —— 改完下次运行即生效。 | | `encodingArgs` | UTF-8 一组 | **控制台编码参数。** 默认就是一组 UTF-8(`file.encoding`、`sun.jnu.encoding`、`sun.stdout.encoding`、`sun.stderr.encoding`、`console.encoding`)—— 缺省即生效、不写进 `launch.json`;显式写空数组 `[]` 表示「不设置」。面板里是「控制台编码方式」下拉框(UTF-8 / GBK / 不设置 / 自定义),选中后启动时会先切换控制台代码页(Windows 上 `chcp 65001` / `936`)再注入参数。列表格式同 `extraClasspath`。参数同样追加到 Maven 构建的 `MAVEN_OPTS`;`vmArgs` 里手写的同名 `-D` 始终优先。JDK 19+ 的标准名(`stdout.encoding` / `stderr.encoding`)在缺省时自动补上。 | | `profiles` | `""` | `spring.profiles.active`。 | | `port` | `""` | 覆盖 `server.port`。 | | `workingDir` | `""` | `java` 进程的工作目录(留空 = 模块目录)。 | | `env` | `""` | 额外的 `KEY=VALUE` 环境变量,**每行一条**;不换行时仍按分号分隔(兼容旧写法)。 | | `envFile` | `""` | 要加载的 `.env` 文件路径。 | | `javaHome` | `""` | Maven 构建与应用启动**共用的 JDK 目录**(留空用 PATH 上的 `java`)。 | | `debugPort` | `"5005"` | 调试时使用的 JDWP 端口。 | | `suspend` | `true` | 调试时挂起 JVM,等待调试器附加。 | | `jrebel` | `false` | 自动探测本机安装的 JRebel 扩展并注入 `-agentpath:…/jrebel64.dll`。 | | `force` | `false` | 即使没有改动也重新构建(忽略构建缓存)。 | | `clean` | `false` | 构建前先执行 `mvn clean`。 | | `skipTests` | `true` | 构建时跳过测试。 | | `offline` | `false` | Maven 离线模式(`-o`)。 | ### 额外 classpath(数量不限) 面板里填 **额外 classpath** 即可,一行一条: ```jsonc "extraClasspath": [ "${workspaceFolder}/libs/fastjson.jar", "${workspaceFolder}/other-module/target/classes" ] ``` 每一条都会追加到运行器从 Maven 解析出的类路径(应用 classes + reactor 兄弟模块 + 已解析依赖) 之后。不存在的条目只在终端里给出警告,不会中断运行。 ### Gradle 项目 构建系统自动判定:根目录(或子目录)有 `pom.xml` 走 Maven,只有 `build.gradle(.kts)` 走 Gradle —— 纯 Gradle 项目不会因为 PATH 上没有 `mvn` 而失败。 - **构建**:优先用项目自己的 `gradlew` wrapper(版本与项目绑定),其次 PATH 上的 `gradle`; 增量编译跑 `classes`(`clean` → `clean classes`,`force` → `--rerun-tasks`, `offline` → `--offline`)。 - **类路径**:不改你的 build.gradle —— 运行器临时注入一个 init script,注册 `sbrClasspath` 任务打印 `sourceSets.main.runtimeClasspath`(IDEA 的做法类似,只是它走自己的模型),用完即删。 - **产物目录**:Gradle 的 class 落在 `build/classes/{java,kotlin,groovy}/main`,与 Maven 的 `target/classes` 不同;构建缓存与“跳过构建”的判定按对应目录找,所以增量体验一致。 - **编码参数**同时进 `GRADLE_OPTS`(Gradle 的守护进程与构建脚本读它),不只 `MAVEN_OPTS`。 ### Kotlin / Groovy 与主类下拉 自动探测主类时扫描 `.java` / `.kt` / `.groovy` 三种扩展名,且覆盖 `src/main/{java,kotlin,groovy}` 三个源码目录 —— Kotlin Spring 项目也能自动找到主类。 面板里的主类输入框带 **datalist 下拉**:按当前选中的模块过滤候选,既能一键选、也能自由输入 (手输的值会保留在列表里)。主类写错只能到运行期才暴露,下拉把这类错误提前消掉。 ### 运行测试 `Spring Boot Runner: 运行测试…` 会在选定的配置上跑测试然后**直接退出**:不组装类路径、 不写构建缓存、不启动 java。过滤串(`com.foo.BarTest`、`com.foo.*Test`)可留空跑全部, 输入过一次后会作为下次的默认值。 实现上是 `mvn test -Dtest=<过滤> -DfailIfNoTests=false` / `gradle test --tests <过滤>`; `skipTests` 在测试模式下自动失效(否则一个测试都不会执行)。 ### JDK 版本预检 启动前读一次 `pom.xml`(或 `build.gradle`)里的 `java.version` / `maven.compiler.release` / `sourceCompatibility`,并与本次要用的 `java` 实际版本对比。**JVM 低于项目要求**时给一条 带操作入口的警告(“设置 JDK…” / “打开运行/调试配置”),而不是等你撞上 “不支持的发行版” / `UnsupportedClassVersionError` 再回头找原因。 读不到要求版本或测不到实际版本就**不提示** —— 宁可不说话,也不误报;警告与构建并行, 不阻塞启动。 ### 控制台编码方式 —— 修复中文乱码 不注入任何参数时,Windows 上 JVM 按 `native.encoding`(中文系统 = GBK)编码 stdout, 而 VS Code 集成终端按 UTF-8 解码 —— 中文日志必然乱码(`鍵鐩兼枟` 那种)。 所以本扩展**默认就按 UTF-8 处理**,分两步: 1. 启动时先在终端执行 `chcp 65001`(GBK 档是 `chcp 936`),把 Windows 控制台的代码页切过去; 2. 再把一组编码参数注入应用 JVM。参数同时进 `MAVEN_OPTS`,Maven 自己的输出 (编译警告、依赖下载日志)也能正确解码。 面板里选的就是「控制台编码方式」: - **UTF-8(推荐,默认)** —— 代码页 `65001` + 一组 `-D…=UTF-8`; - **GBK(控制台切到 936)** —— 给必须跟系统代码页走的外部终端; - **不设置** —— 既不切代码页也不注入参数(写进 `launch.json` 的是空数组, 不会被默认值重新填回来); - **自定义** —— 切到逐项勾选:`file.encoding`、`sun.jnu.encoding`、`sun.stdout.encoding`、 `sun.stderr.encoding`、`console.encoding`(取当前值,默认 UTF-8)。 JDK 19 起 `sun.stdout.encoding` / `sun.stderr.encoding` 改名成了标准属性 `stdout.encoding` / `stderr.encoding`(JEP 400),运行器会在缺标准名时自动补一条 —— JDK 8 ~ 25 一套配置通吃。若 `vmArgs` 已手写同名 `-Dkey`,以手写值为准,注入项被跳过 (终端里有日志)。 ### 不写死路径的 JRebel(热部署) 在面板勾选 **自动检测 JRebel**(或在 `launch.json` 里写 `"jrebel": true`),扩展会在运行时 找到 native agent 并把 `-agentpath:<绝对路径>` 追加到 JVM 参数。这样就不用手写 ``` -agentpath:C:\\Users\\me\\.vscode\\extensions\\jrebelbyperforcesoftware.jrebel-2026.3.2\\agent\\jrebel\\lib\\jrebel64.dll ``` ——那种写法在 JRebel 升级、目录名里的版本号变化的瞬间就悄悄失效(JVM 会以 `Invalid -agentpath argument` 拒绝启动)。 查找顺序,命中即止: 1. `springBootRunner.jrebelAgentPath` 设置 —— 显式覆盖。 2. `vscode.extensions.all` —— 宿主自己的列表,拿到的是真实的 `extensionPath`。 3. 宿主的 `extensions.json` 清单 —— `identifier.id` + `version` + 位置的权威来源, 即使 JRebel 已安装但在本窗口被禁用也能生效。 4. 扩展目录名扫描 —— 最后的兜底。 来源 2 和 3 带版本号,因此装了多个 JRebel 构建时取最新的。目录名扫描不假设任何命名格式: 同一个 JRebel 在 `.vscode` 里叫 `jrebelbyperforcesoftware.jrebel-2026.3.2`, 在 CodeBuddy 里叫 `jrebelbyperforcesoftware.jrebel-2026.3.2-universal`。 注意: - 选中的文件同时匹配当前平台**与 CPU 架构**(`jrebel64.dll` / `libjrebel64.so` / `libjrebelaarch64.so` / `libjrebel64.dylib`);载入错误的那一个会让 JVM 报 bad-ELF / cannot-load 失败。 - 若 `vmArgs` 里已经有 `-agentpath:…jrebel…` 或 `-Drebel.home=…`,注入会被**跳过** ——同一个 native agent 加载两次会让 JVM 中止。发生时你会收到提示。 - `rebel.xml` 仍由 JRebel 自己的向导生成,这里只负责挂载 agent。 - 启动配置时若什么都没找到,会给出带 **设置 agent 路径** 操作的警告,而不是静默失败。 ## 面板里的运行 / 已停止状态 正在运行的配置名前会显示绿色 `●`,并把 ▶ / 🐞 换成红色**停止**按钮(■),无需悬停始终可见。 行尾还有一个 **⊗ 清空控制台** 按钮,一键清掉该配置自己的终端输出。点击停止只会杀掉该配置的 进程树 —— 其它配置继续运行。 点下运行 / 调试的那一刻就进入「运行中」:PID 文件是在构建**开始之前**写下的,所以构建还没 跑完时按钮已经是停止态,不会出现手快连点两下、同一终端里叠进两个构建的情况;这时点停止 会连带打断构建。 状态来自运行器写下的按配置区分的 PID 文件 (`%TEMP%/spring-boot-runner--.pid`)加存活检查,因此应用自己退出或被外部 强杀时,指示灯会自行熄灭。每 2 秒轮询一次 —— 进程死亡不像调试会话那样有可订阅的事件。 底部**状态栏**里还有一条同名指示:有服务运行时显示 `$(debug-alt) 配置名`(多条时显示数量) 并铺上橙色底,点击聚焦该配置的集成终端(多条时弹选择框);一个都没跑时整条隐藏,不占位置。 它同样按 PID 文件每 2 秒重算,因此侧边栏收起、或同时跑着好几条配置时也一眼可见。 ### 打开面板时它扫什么、有多慢 配置列表(读 `launch.json`)是瞬时的,**先发**;「模块」下拉和「主类」datalist 需要扫描仓库 源码,是这里最慢的一步 —— 700+ 个 java 文件的仓库实测 0.5~1 秒,而旧实现用同步读文件, 还会阻塞扩展宿主(连累其它扩展一起卡),能拖到 4~20 秒。所以: - 扫描结果**缓存**:收起再展开侧边栏、或 webview 被 VS Code 回收后重开,都直接复用; - 只有 `pom.xml` / `build.gradle` / `settings.gradle` 出现变化、或切换工作区才重扫; - 扫描本身已改为异步并发读取(每批 16 个文件、在 Buffer 里搜标记),不再阻塞 VS Code; - 树工具栏的 **⟳ 重新扫描** 可随时手动丢掉缓存重扫(改完构建结构、pom 监视没触发时用), 状态行会给出开始 / 完成反馈。 `request: "attach"` 字段:`hostName`(默认 `localhost`)、`port`(默认 `5005`)、`timeout`。 attach 配置既不构建也不启动任何东西 —— 只附加调试器,也不会为本扩展创建额外的会话。 所有字符串字段都接受常见的 launch.json 变量 —— `${workspaceFolder}`、 `${workspaceFolderBasename}`、`${userHome}`、`${env:NAME}` —— 无论走 F5(由 VS Code 替换) 还是从运行/调试配置面板启动(由扩展自己替换)。 文件通过 jsonc 编辑,因此**其它调试器(`java`、`node`、`chrome` …)的条目和你自己写的注释都会 保留**。`type` 不是 `spring-boot-runner` 的条目绝不会被碰;无法解析的 `launch.json` 只会被报告, 不会被覆盖。 > 早期版本把配置存在 `.vscode/spring-boot-runner.json`、读平铺的 `springBootRunner.*` 设置。 > 两者**都不再读写** —— `.vscode/launch.json` 是唯一真源。 ### `javaHome`(JDK 选择) `javaHome` 会把 `java` 解析为 `/bin/java`,**并且**为每次 Maven 调用导出 `JAVA_HOME=`(同时把 `/bin` 前置到 PATH),于是 `mvn install` 与启动应用用 的是同一个 JDK —— `mvn.cmd` 正是通过 `JAVA_HOME` 选 java 的。当集成终端里的 `java` 版本不对时 很有用(比如项目要 JDK 21 而 PATH 上是 JDK 17)。 三种设置方式: - **面板** —— `运行/调试配置…` → `JDK(JAVA_HOME,留空用 PATH 上的 java)` 字段。 - **命令** —— `Spring Boot Runner: 设置 JDK(JAVA_HOME)…` 提示输入路径并存入选定的运行配置。 - **launch.json** —— 直接写 `"javaHome": "C:\\Program Files\\Java\\jdk-21"`。 ## Maven 页签:模块树与依赖图 - **模块树**按聚合 / 父子关系嵌套(`` 优先,其次 `` 的 `relativePath`, 再不行按坐标匹配):聚合模块默认展开、叶子默认折叠;工具栏的 `⊞ 展开` / `⊟ 折叠` 一键整树展开或压平。 - 每个模块下:生命周期阶段(hover 出现 ▶ 可直接在终端跑)、声明的插件、依赖项 (打开视图后在后台自动跑 `mvn dependency:tree` 解析,无需手动触发;pom 改动后自动重跑)。 - 树由 [`@vscode-elements/elements`](https://vscode-elements.github.io/) 的 `` 渲染:缩进引导线、展开/选中态与 VS Code 原生树一致,展开/折叠走 组件原生的 `expandAll()` / `collapseAll()`。 - 工具栏的 **⛓ 依赖图** 在**编辑器区**打开一个面板:左边是模块依赖图(节点 = reactor 模块,边 = 模块间依赖,含传递),右边搜索 `groupId:artifactId`(或关键字)会列出 「这个包是从哪条链被引进来的」,每条链按 A → B → C 画出来,`omitted` 的原因在悬停提示里。 - pom 扫描(`detectMavenWorkspace`)与 `dependency:tree` 结果都有缓存:按 pom 文件集合 + mtime 判脏,没动 pom 就不会重扫;需要时用 `⟳` 手动强制重跑。 ## 活动栏入口 扩展贡献了一个**活动栏**容器(叶子+播放图标,`media/activity-icon.svg`),名字叫 **Spring Boot Runner**。点开是一个带一键链接的视图:运行 / 调试 / 自定义参数 / 停止。 注意:VS Code 把活动栏图标渲染成**单色**(主题前景色)—— 彩色的 `images/icon.png` 只会在扩展 面板里显示,永远不会出现在活动栏。顶层 `icon` 字段与活动栏图标是两套独立机制。 ## 设置 只剩三个安装级设置;一切描述*一次运行*的内容都在 `launch.json` 里。可通过设置界面或 `.vscode/settings.json` 配置: | 键 | 默认值 | 含义 | | --- | --- | --- | | `springBootRunner.nodePath` | `""` | 运行 `run-spring-boot.mjs` 用的 Node.js 解释器(留空用 PATH 上的 `node`)。只有集成终端里找不到 `node` 时才需要填绝对路径。 | | `springBootRunner.runnerScript` | `null` | `run-spring-boot.mjs` 的路径(默认用 `scripts/` 下随扩展打包的副本)。 | | `springBootRunner.jrebelAgentPath` | `""` | JRebel native agent 路径的覆盖。**留空即可** —— 自动探测已覆盖常规情况;只有 JRebel 装在搜索够不到的地方才需要填。支持 `${userHome}`。 | ## 停止是怎么工作的 启动时 `run-spring-boot.mjs` 把自己的 PID 写入 `%TEMP%/spring-boot-runner-[-<配置 id>].pid` —— 后缀用配置的**稳定 id**: `ConfigStore` 在读到缺少 id 的旧条目时一次性补写进 `launch.json`,F5 与面板拿到的是同一个值, 因此同名配置、改名、复制都不会互相覆盖 PID 与 `@argfile`(旧版本按配置名命名的 pid 文件 在停止时会顺带清一次)。 `停止` **先礼后兵**,给应用留出跑 shutdown hook 的机会: 1. 分离调试器; 2. 向**本扩展自己的**集成终端发 Ctrl-C —— shell 转给前台的 node + java,JVM 得以执行 `@PreDestroy`、关闭连接池、落盘日志(这正是直接 `taskkill /F` 会跳过的部分), 绝不碰你自己开的终端; 3. 轮询最多 8 秒; 4. 没退:Unix 给整个进程组发 SIGTERM(java 与 node 同组,JVM 仍能优雅退出), Windows 试一次不带 `-F` 的 taskkill,再等 3 秒; 5. 还没退才强杀(`taskkill /PID /T /F` / `SIGKILL`),最后清理 PID 文件。 在 `spring-boot-runner` 调试会话里按 **Stop** 按钮也会触发同样的清理,因此 F5 启动的应用也能 从调试工具栏停止。 ## 界面语言 / Language - **扩展描述与全部字段提示**(命令面板、设置页、IntelliSense)走 VS Code 官方的 `package.nls.json`(English)/ `package.nls.zh-cn.json`(中文),按显示语言自动切换; Marketplace 也会按访问者的语言显示 displayName 与 description。 - **配置面板与提示消息**由 `src/i18n.ts` 的双语表驱动,同样跟随 VS Code 显示语言。 - **本文档**单文件内双语:顶部 `中文 / English` 点击切换,两版章节一一对应。 - **Descriptions and every field hint** (command palette, settings, IntelliSense) use the official `package.nls.json` (English) / `package.nls.zh-cn.json` (Chinese) and follow the display language; the Marketplace shows displayName / description in the visitor's language too. - **The configuration panel and host messages** are driven by the bilingual table in `src/i18n.ts`, also following the VS Code display language. - **This document** carries both languages in one file: use `中文 / English` at the top to switch; the two sides have matching sections. ## 构建与安装(自己执行) ```bash cd spring-boot-runner-vscode npm install # 同时安装 jsonc-parser(用于安全编辑 launch.json) npm run compile # 产出 dist/extension.js # 生成可安装的 .vsix: npx @vscode/vsce package # 需要 @vscode/vsce,会按需安装 ``` 然后在 VS Code 里:**扩展 → … → 从 VSIX 安装…**,选中生成的 `.vsix`。 本地开发时在该目录按 `F5` 即可启动「扩展开发宿主」。 ### 发布到 Open VSX Open VSX(open-vsx.org)是 VSCodium / Cursor / Windsurf 等非微软发行版使用的注册表。 ```bash # 令牌放在仓库根的 .ovsx-token(已在 .vscodeignore 里,不进 vsix、不入库) npx ovsx publish spring-boot-runner-.vsix --pat "$(cat .ovsx-token)" ``` - 令牌在 生成(需先登录并 claim `boluoshu` 命名空间); - **版本号必须递增** —— Open VSX 不允许覆盖已存在的版本; - 发布后服务端要异步校验与索引,约 1 分钟后 `https://open-vsx.org/api/boluoshu/spring-boot-runner` 才会返回新版本(`ovsx` 当场就打印 Published)。 --- # Spring Boot Runner (plugin-free) — VS Code Extension > 🌐 **Language:** [Chinese](#chinese) | **English** A VS Code extension that runs an **unpackaged** Spring Boot (Maven) project straight from the command line via `java -cp`, without the `spring-boot-maven-plugin` and without the official VS Code Spring Boot plugin. It is a thin UI over `scripts/run-spring-boot.mjs`. One core script, one entry point: - **VS Code extension** → human clicks a command in the editor. The runner is pure Node (ESM), so **no Python install is required** — only a Maven on PATH and a JDK. ## Source repository Gitee: `https://gitee.com/boluoshu/spring-boot-runner` (branch `master`) ```bash git clone https://gitee.com/boluoshu/spring-boot-runner.git ``` ## Why The Spring Boot Dashboard "runs" an app by resolving the `@SpringBootApplication` main class + classpath, then calling `java -cp `. This extension reproduces that with plain command-line tools (`maven-dependency-plugin` instead of the IDE's JDT classpath resolver) and never needs `spring-boot-maven-plugin`. The long classpath is written to a per-project `@argfile` in the temp dir, so the launched command stays tiny and avoids the Windows `CreateProcess` command-line length limit. ## Commands | Command | What it does | | --- | --- | | `Spring Boot Runner: Run Project` | Run with a run configuration from `.vscode/launch.json`. Asks which one when several exist; falls back to auto-detect when none does. | | `Spring Boot Runner: Run Tests…` | Run tests and exit without launching java: `mvn test` / `gradle test`, with an optional filter (e.g. `com.foo.*Test`) that is remembered. | | `Spring Boot Runner: Run with Debug (JDWP)` | Same, but launches with JDWP (`suspend` per configuration) and auto-attaches VS Code's Java debugger, so breakpoints work immediately. | | `Spring Boot Runner: Run with Custom Args` | Prompt for extra args (e.g. `--profiles dev --port 8080`), then run. Quote paths with spaces; `--java-home` here overrides the JDK for this run only. | | `Spring Boot Runner: Configure Java Home…` | Prompt for a JDK home (validated against `bin/java(.exe)`) and store it in the chosen run configuration. Empty input clears it. | | `Spring Boot Runner: Stop` | Stop a running/debugging app: detaches the debugger, kills the Java process tree via the PID file (`taskkill /PID … /T /F`), and sends Ctrl-C to the terminal as a fallback. | Output runs in a reusable integrated terminal named **Spring Boot Runner**. ## Run/debug configurations — `.vscode/launch.json` Run/debug configurations live in the project's **`.vscode/launch.json`**, under this extension's own debugger type `spring-boot-runner`. They are ordinary debug configurations, so the same file drives three entry points: - the **Run and Debug** view (dropdown in the sidebar), - **F5** (run with debugger) and **Ctrl+F5** (run without debugger), - the **Run/Debug Configurations…** panel contributed by this extension (an IDEA-style form). `Ctrl+Shift+P → Spring Boot Runner: Run/Debug Configurations…` opens the panel: a tree of configurations on the left, the selected configuration's form on the right. There is no settings file to edit by hand for run parameters — the panel *is* an editor for `launch.json`. ```jsonc { "version": "0.2.0", "configurations": [ { "type": "spring-boot-runner", "request": "launch", "name": "App 1", "module": "backend/bonlink-agent", // empty = auto-detect "mainClass": "com.bonlink.PlatformApplication", // empty = auto-detect "profiles": "dev", "port": "8081", "vmArgs": "-Xmx1g -Denv=dev", "env": "SPRING_DATASOURCE_URL=jdbc:mysql://localhost/db", "javaHome": "D:\\soft\\Java\\jdk-21.0.8+9", "skipTests": true }, { "type": "spring-boot-runner", "request": "attach", // runs nothing, only attaches "name": "Remote JVM debug 1", "hostName": "10.0.0.5", "port": 5005 } ] } ``` `request: "launch"` fields: | Field | Default | Meaning | | --- | --- | --- | | `module` | `""` | Build module directory (Maven pom.xml or Gradle build.gradle(.kts); empty = auto-detect). | | `mainClass` | `""` | Fully-qualified main class (empty = auto-detect from `@SpringBootApplication`; the panel offers a dropdown of candidates). | | `programArgs` | `""` | Program arguments, **one per line**; a single line still splits on spaces (legacy form). | | `vmArgs` | `""` | Extra JVM arguments, **one per line**; a single line still splits on spaces (legacy form). | | `extraClasspath` | `[]` | **Extra classpath entries — as many as you like.** JSON array (one entry per line in `launch.json`) or a single string split on newlines/`;`. Appended **after** the Maven-resolved classpath, so missing jars / another module's `target/classes` can be added without disturbing existing order. Relative paths resolve against the project root; `${workspaceFolder}` etc. work. Not part of the build cache — changes apply on the next run. | | `encodingArgs` | a UTF-8 set | **Console encoding args.** Defaults to a UTF-8 set (`file.encoding`, `sun.jnu.encoding`, `sun.stdout.encoding`, `sun.stderr.encoding`, `console.encoding`) — the default applies without writing anything to `launch.json`; an explicit empty array `[]` means "do not set". The panel exposes a *console encoding* dropdown (UTF-8 / GBK / none / custom) that also switches the Windows console code page (`chcp 65001` / `936`) before launching. Same list format as `extraClasspath`. Also appended to the Maven build's `MAVEN_OPTS`; a hand-written `-D` in `vmArgs` always wins. The JDK 19+ standard names (`stdout.encoding` / `stderr.encoding`) are filled in automatically when missing. | | `profiles` | `""` | `spring.profiles.active`. | | `port` | `""` | Override `server.port`. | | `workingDir` | `""` | Working directory of the `java` process (empty = the module directory). | | `env` | `""` | Extra `KEY=VALUE` env vars, **one per line**; a single line still splits on semicolons (legacy form). | | `envFile` | `""` | Path to a `.env` file to load. | | `javaHome` | `""` | **JDK home** for both the Maven build and the launched app (empty = `java` on PATH). | | `debugPort` | `"5005"` | JDWP port used when debugging. | | `suspend` | `true` | When debugging, suspend the JVM until the debugger attaches. | | `jrebel` | `false` | Auto-detect the locally installed JRebel extension and inject `-agentpath:…/jrebel64.dll`. | | `force` | `false` | Rebuild even when nothing changed (ignore the build cache). | | `clean` | `false` | Run `mvn clean` before building. | | `skipTests` | `true` | Skip tests during the build. | | `offline` | `false` | Maven offline mode (`-o`). | ### Extra classpath (unlimited entries) Tick nothing special — just fill **额外 classpath** in the panel, one entry per line: ```jsonc "extraClasspath": [ "${workspaceFolder}/libs/fastjson.jar", "${workspaceFolder}/other-module/target/classes" ] ``` Each entry is appended to the classpath the runner built from Maven (app classes + reactor siblings + resolved dependencies). Entries that do not exist are reported as a warning in the terminal but never abort the run. ### Gradle projects The build system is detected automatically: a `pom.xml` (at the root or in a subdirectory) means Maven, only `build.gradle(.kts)` means Gradle — a pure Gradle project no longer fails just because `mvn` is not on PATH. - **Build**: prefers the project's own `gradlew` wrapper (version matches the project), then a `gradle` on PATH; incremental compilation runs `classes` (`clean` → `clean classes`, `force` → `--rerun-tasks`, `offline` → `--offline`). - **Classpath**: your build.gradle is never modified — the runner injects a throwaway init script that registers an `sbrClasspath` task printing `sourceSets.main.runtimeClasspath` (same idea as what IDEA does with its own model), then deletes it. - **Output dirs**: Gradle classes live in `build/classes/{java,kotlin,groovy}/main`, not `target/classes`; the build cache and skip-build checks look at the matching directory, so incremental behaviour stays the same. - **Encoding arguments** also go into `GRADLE_OPTS` (what the Gradle daemon and build scripts read), not just `MAVEN_OPTS`. ### Kotlin / Groovy and the main-class dropdown Auto-detection scans `.java`, `.kt` and `.groovy` across `src/main/{java,kotlin,groovy}` — Kotlin Spring projects get a detected main class too. The main-class field is a **datalist dropdown**: candidates are filtered by the currently selected module, so you can pick with one click or type freely (typed values stay in the list). A wrong FQCN used to surface only at run time; the dropdown moves that failure earlier. ### Running tests `Spring Boot Runner: Run Tests…` runs the tests on the chosen configuration and **exits**: no classpath assembly, no build-cache write, no java launch. The filter (`com.foo.BarTest`, `com.foo.*Test`) may be empty to run everything, and the last value becomes the default next time. Under the hood it is `mvn test -Dtest= -DfailIfNoTests=false` / `gradle test --tests `; `skipTests` is ignored in test mode (otherwise no test would run at all). ### JDK version preflight Before launching, the runner reads `java.version` / `maven.compiler.release` / `sourceCompatibility` from `pom.xml` (or `build.gradle`) and compares it with the actual `java` about to be used. When the **JVM is older than the project requires** you get an actionable warning (Set JDK… / Open Run/Debug Configurations) instead of hitting an unsupported release or `UnsupportedClassVersionError` and only then hunting for the cause. If the required version cannot be read, or the actual one cannot be measured, **nothing is shown** — no warning beats a wrong one. The check runs in parallel with the build and never blocks the launch. ### Console encoding — fixing garbled Chinese logs With no arguments injected, a JVM on Windows encodes stdout using `native.encoding` (GBK on a Chinese system) while the VS Code integrated terminal decodes as UTF-8 — Chinese log lines come out as `鍵鐩兼枟`-style mojibake. So this extension defaults to UTF-8, in two steps: 1. On launch the terminal first runs `chcp 65001` (`chcp 936` for the GBK option) to switch the Windows console code page; 2. then a set of encoding arguments is injected into the app JVM. The same arguments go to `MAVEN_OPTS`, so Maven's own output (compile warnings, dependency downloads) decodes correctly too. The panel dropdown is the **console encoding**: - **UTF-8 (recommended, default)** — code page `65001` plus a set of `-D…=UTF-8`; - **GBK (console switched to 936)** — for external terminals that must keep the system code page; - **None** — neither switches the code page nor injects anything (written to `launch.json` as an empty array, so the default does not creep back in); - **Custom** — tick items individually: `file.encoding`, `sun.jnu.encoding`, `sun.stdout.encoding`, `sun.stderr.encoding`, `console.encoding` (all take the current value, UTF-8 by default). Since JDK 19, `sun.stdout.encoding` / `sun.stderr.encoding` were renamed to the standard properties `stdout.encoding` / `stderr.encoding` (JEP 400); the runner adds the standard name automatically when missing, so one configuration works across JDK 8 ~ 25. If `vmArgs` already sets the same `-Dkey`, your hand-written value wins and the injected one is skipped (logged in the terminal). ### JRebel (hot reload) without hard-coding a path Tick **自动检测 JRebel** in the panel (or write `"jrebel": true` in `launch.json`) and the extension finds the native agent at run time and appends `-agentpath:` to the JVM arguments. This replaces hand-writing ``` -agentpath:C:\\Users\\me\\.vscode\\extensions\\jrebelbyperforcesoftware.jrebel-2026.3.2\\agent\\jrebel\\lib\\jrebel64.dll ``` which silently breaks the moment JRebel updates and the version number in the directory name changes (the JVM then refuses to start with `Invalid -agentpath argument`). Search order, first hit wins: 1. `springBootRunner.jrebelAgentPath` setting — explicit override. 2. `vscode.extensions.all` — the host's own list, so the real `extensionPath` is used. 3. The host's `extensions.json` manifest — authoritative `identifier.id` + `version` + location, and it works even when JRebel is installed but disabled in this window. 4. Directory-name scan of the extension folders — last-resort fallback. Sources 2 and 3 carry a version, so when several JRebel builds are installed the newest wins. The scan never assumes a directory-name shape: the same JRebel is `jrebelbyperforcesoftware.jrebel-2026.3.2` in `.vscode` but `jrebelbyperforcesoftware.jrebel-2026.3.2-universal` in CodeBuddy. Notes: - The file picked matches the current platform **and CPU arch** (`jrebel64.dll` / `libjrebel64.so` / `libjrebelaarch64.so` / `libjrebel64.dylib`); loading the wrong one makes the JVM fail with a bad-ELF / cannot-load error. - If your `vmArgs` already contain `-agentpath:…jrebel…` or `-Drebel.home=…`, injection is **skipped** — loading the same native agent twice aborts the JVM. You are told this happened. - `rebel.xml` is still generated by JRebel's own wizard; this only mounts the agent. - When the run configuration is started and nothing was found, you get a warning with a **Set agent path** action rather than a silent no-op. ## Running / stopped state in the panel A configuration that is currently running shows a green `●` before its name and replaces the ▶ / 🐞 pair with a red **stop** button (■), always visible without hovering. The row also carries a **⊗ clear-console** button that wipes this extension's terminal output. Clicking stop kills only that configuration's process tree — other configurations keep running. The state comes from the per-configuration PID file the runner writes (`%TEMP%/spring-boot-runner--.pid`) combined with a liveness check, so the indicator clears itself when the app exits on its own or is killed externally. It is polled every 2 s because process death, unlike a debug session, emits no event we can subscribe to. A status-bar entry mirrors that state: while anything is running it shows `$(debug-alt) ` (or a count) on an orange background and hides completely when nothing is up; clicking it focuses the matching terminal (a picker appears when several are running). ### What the panel scans, and how slow it is The configuration list (a `launch.json` read) is instant and is sent **first**; the *module* dropdown and the *main class* datalist need a source scan, which is the slow part here — measured at 0.5–1 s for a repo with 700+ java files, while the old synchronous file reads also blocked the extension host (dragging other extensions down with it) and could take 4–20 s. Therefore: - the scan result is **cached**: collapsing and re-opening the sidebar, or a webview recycled by VS Code, reuses it instead of scanning again; - only a change to `pom.xml` / `build.gradle` / `settings.gradle`, or a workspace switch, invalidates the cache; - the scan itself now reads files asynchronously in batches of 16 and searches raw Buffers, so it no longer blocks VS Code; - the **⟳ rescan** button in the tree toolbar drops the cache and scans again on demand (useful after restructuring a build when the pom watcher did not fire), reporting start and finish in the status line. `request: "attach"` fields: `hostName` (default `localhost`), `port` (default `5005`), `timeout`. An attach configuration neither builds nor starts anything — only the debugger is attached, and no extra session is created for this extension. All string fields accept the usual launch.json variables — `${workspaceFolder}`, `${workspaceFolderBasename}`, `${userHome}`, `${env:NAME}` — both when started via F5 (VS Code substitutes them) and when started from the Run/Debug Configurations panel (the extension substitutes them itself). The file is edited through jsonc edits, so **entries of other debuggers (`java`, `node`, `chrome`, …) and any comments you wrote are preserved**. Entries whose `type` is not `spring-boot-runner` are never touched, and a `launch.json` that does not parse is reported instead of being overwritten. > Earlier versions stored configurations in `.vscode/spring-boot-runner.json` and read flat > `springBootRunner.*` settings. Both are **no longer read or written** — `.vscode/launch.json` is > the single source of truth. ### `javaHome` (JDK selection) `javaHome` resolves `java` as `/bin/java` **and** exports `JAVA_HOME=` (plus `/bin` first on PATH) for every Maven invocation, so `mvn install` compiles with the same JDK that launches the app — `mvn.cmd` picks its java via `JAVA_HOME`. Useful when the integrated terminal's `java` is the wrong version (e.g. the project needs JDK 21 but PATH has JDK 17). Three ways to set it: - **Panel** — `Run/Debug Configurations…` → the `JDK (JAVA_HOME)` field. - **Command** — `Spring Boot Runner: Configure Java Home…` prompts for the path and stores it in a chosen run configuration. - **launch.json** — write `"javaHome": "C:\\Program Files\\Java\\jdk-21"` directly. ## Maven view: module tree and dependency graph - The **module tree** nests by aggregation / inheritance (`` first, then the `` `relativePath`, then coordinate matching): aggregators start expanded and leaves start collapsed; the toolbar's `⊞ Expand` / `⊟ Collapse` opens or flattens the whole tree. - Under each module: lifecycle phases (hover for a ▶ to run in the terminal), declared plugins, and dependencies (resolved in the background from a real `mvn dependency:tree` as soon as the view opens, and re-run automatically after a pom change). - The tree is rendered with the `` component from [`@vscode-elements/elements`](https://vscode-elements.github.io/), so indent guides and expand/selection states match the native VS Code tree. - The **⛓ Graph** button opens a panel **in the editor area**: on the left a module dependency graph (nodes = reactor modules, edges = inter-module dependencies incl. transitive ones), and a search box that lists how a package is pulled in — each hit is drawn as the A → B → C chain, with `omitted` reasons in the tooltip. - Both the pom scan (`detectMavenWorkspace`) and the `dependency:tree` results are cached against the set of pom files plus their mtimes, so nothing is re-scanned until a pom actually changes; the `⟳` buttons force a rebuild. ## Activity Bar entry The extension contributes an **Activity Bar** container (leaf+play icon, `media/activity-icon.svg`) named **Spring Boot Runner**. Clicking it opens a view with one-click links for Run / Debug / Custom Args / Stop. Note: VS Code renders Activity Bar icons **monochrome** (theme foreground color) — the colored `images/icon.png` only shows in the Extensions panel, never in the Activity Bar. The top-level `icon` field and the Activity Bar icon are two independent mechanisms. ## Settings Only three installation-level settings remain; everything that describes a *run* lives in `launch.json`. Set via the Settings UI or `.vscode/settings.json`: | Key | Default | Meaning | | --- | --- | --- | | `springBootRunner.nodePath` | `""` | Node.js interpreter for `run-spring-boot.mjs` (empty = `node` on PATH). Set an absolute path only if `node` is not resolvable from the integrated terminal. | | `springBootRunner.runnerScript` | `null` | Path to `run-spring-boot.mjs` (defaults to the bundled copy under `scripts/`). | | `springBootRunner.jrebelAgentPath` | `""` | Override for the JRebel native agent path. **Leave empty** — auto-detection covers the normal cases; use this only when JRebel lives somewhere the search cannot reach. Supports `${userHome}`. | ## How Stop works On start, `run-spring-boot.mjs` writes its PID to `%TEMP%/spring-boot-runner-[-].pid` — the suffix is the configuration's **stable id**: `ConfigStore` backfills it into `launch.json` once for older entries that lack one, and F5 plus the panel resolve the same value, so same-named configurations, renames and duplicates never overwrite each other's PID file or `@argfile` (pid files named after the configulation name by older versions are cleaned up as well). `Stop` **asks before it kills**, giving the app room to run its shutdown hooks: 1. detach the debugger; 2. send Ctrl-C to **this extension's own** integrated terminal — the shell forwards it to the foreground node + java, so the JVM can run `@PreDestroy`, close connection pools and flush logs (exactly what a bare `taskkill /F` skips). Terminals you opened yourself are never touched; 3. poll for up to 8 s; 4. still alive: SIGTERM to the whole process group on Unix (java shares it with node, so the JVM still exits cleanly), or `taskkill` without `-F` on Windows, then wait 3 more seconds; 5. only then force-kill (`taskkill /PID /T /F` / `SIGKILL`) and remove the PID file. The same cleanup runs when you press the **Stop** button of a `spring-boot-runner` debug session, so F5-started apps are stopped from the debug toolbar too. ## Language - **Descriptions and field hints** (command palette, settings, IntelliSense) use the official `package.nls.json` (English) / `package.nls.zh-cn.json` (Chinese) and follow the display language; the Marketplace shows displayName / description in the visitor's language as well. - **The configuration panel and host messages** are driven by the bilingual table in `src/i18n.ts`, following the VS Code display language. - **This document** carries both languages in one file: use `中文 / English` at the top to switch; the two sides have matching sections. - **扩展描述与字段提示**走 `package.nls.*` 双份,按显示语言自动切换;Marketplace 按访问者语言 显示 displayName 与 description。 - **配置面板与提示消息**由 `src/i18n.ts` 双语表驱动,同样跟随 VS Code 显示语言。 - **本文档**单文件双语,顶部 `中文 / English` 点击切换,章节一一对应。 ## Build & install (run these yourself) ```bash cd spring-boot-runner-vscode npm install # also installs jsonc-parser (used to edit launch.json safely) npm run compile # emits dist/extension.js # To produce a .vsix you can install in VS Code: npx @vscode/vsce package # needs @vscode/vsce; installs on demand ``` Then in VS Code: **Extensions → … → Install from VSIX…**, pick the generated `.vsix`. For local dev, press `F5` in this folder to launch an Extension Development Host. ### Publishing to Open VSX Open VSX (open-vsx.org) is the registry used by non-Microsoft distributions such as VSCodium, Cursor and Windsurf. ```bash # the token lives in .ovsx-token at the repo root (listed in .vscodeignore: never packaged, never committed) npx ovsx publish spring-boot-runner-.vsix --pat "$(cat .ovsx-token)" ``` - Create the token at (sign in and claim the `boluoshu` namespace first); - **the version must increase** — Open VSX refuses to overwrite an existing version; - after publishing, the server validates and indexes asynchronously; expect about a minute before `https://open-vsx.org/api/boluoshu/spring-boot-runner` reports the new version (`ovsx` itself prints “Published” right away).