我的同事 lgp 哥接手维护了这个项目. https://github.com/lgp547/any-door-plugin. 核心功能(idea方法调用)没有区别, anydoor 的更新和维护更频繁, 使用体验更流畅, 建议使用 anydoor.
本项目会继续维护, 更专注于 clojure nrepl 的使用场景而不是 idea 插件
在目标 JVM 里内嵌一个 Electric Clojure v3 实时 dashboard(websocket 推送,非轮询):
- 实时内存(heap / non-heap / 各 pool 的用量条)、GC、线程数、CPU、uptime
- 已加载 class 正则搜索(类浏览器雏形)
- 浏览器内 eval console(直接 eval 到目标 JVM 的
userns,能调repl.tools/repl.inspect的全部能力)
启动目标应用后访问 http://127.0.0.1:7890 (仅监听 loopback)。
系统属性配置:
-Dnrepl.dashboard.enabled=false— 关闭 dashboard(默认开启)-Dnrepl.dashboard.port=7890— 端口-Dnrepl.dashboard.host=127.0.0.1— 监听地址
运行时要求 JDK 17+(Jetty 12)。JDK 8 的目标进程会自动跳过 dashboard,nrepl 不受影响。
dashboard 的 CLJS 客户端是预编译的(Electric v3 的 client 编译需要 shadow-cljs, 只在构建期需要,运行时和 prod 构建都不需要 hyperfiddle 许可登录):
cd dashboard
clj -X:build build-client # 产物写入 ../src/main/resources/public/nrepl_dashboard/
cd .. && mvn clean package # 重新打 agent jar产物(js + manifest)不入库;jar 里没有这些资源时 dashboard 自动禁用并打印提示。
许可注意:Electric v3 是 BSL(Business Source License)许可——非商业使用免费, 商业使用需确认符合其条款。
启动 nrepl server 后会自动加载 repl.tools 命名空间(以资源形式打在 jar 里),
任何 nrepl 客户端(Calva / agent / 脚本)都可以直接调用,在运行中的 JVM 里
编译、加载、热替换 Java 代码。
(repl.tools/compile-and-load!
"com.example.Greeter"
"package com.example;
public class Greeter {
public static String greet(String name) { return \"hello \" + name; }
}")
;; => {:ok? true :class com.example.Greeter :loaded [...] :diagnostics []}- 编译在内存中完成(
javax.tools.JavaCompiler),不落盘;要求目标进程用 JDK 启动而不是 JRE - 每次调用使用一个全新的 classloader,所以同名 class 可以反复重载,返回的
:class永远是最新定义 - 编译 classpath 默认取目标应用的
java.class.path;也可传:classpath
(repl.tools/redefine!
"com.example.App"
"package com.example;
public class App {
public static int add(int a, int b) { return a * b; } // 方法体从 + 改成 *
...
}")
;; => {:ok? true :redefined 1}
(com.example.App/add 3 4) ;; => 12- 通过
Instrumentation/redefineClasses实现,要求用-javaagent方式启动(手动R.start没有 Instrumentation 句柄) - JVM 限制:只允许 schema 不变的修改(方法体、常量)。加/删方法、字段会返回错误和提示;这种场景请写新 class 用
compile-and-load! - 同一个 class 被多个 classloader 加载时会全部 redefine
(repl.tools/redefine-file! "com.example.App" "src/main/java/com/example/App.java")
(repl.tools/compile-and-load-file! "com.example.Greeter" "scratch/Greeter.java")(repl.tools/find-loaded-classes "com.example.App") ;; 已加载的同名 Class(按 classloader 区分)
(repl.tools/instrumentation) ;; 原始 Instrumentation 句柄repl.inspect 与 repl.tools 一起自动加载。所有函数返回纯 EDN 数据
(map/vector),可以直接在 nrepl 客户端里看,也是未来 dashboard 前端的数据源。
(repl.inspect/memory) ;; heap / non-heap / 各 memory pool 用量
(repl.inspect/gc) ;; 各 GC collector 的回收次数与耗时
(repl.inspect/threads) ;; 线程数、daemon 数、死锁检测
(repl.inspect/thread-dump) ;; 全部线程 + 堆栈(大应用上会很大)
(repl.inspect/runtime) ;; VM 版本、启动参数、uptime
(repl.inspect/system) ;; OS、CPU load、物理内存
;; 以下需要 -javaagent(走 Instrumentation)
(repl.inspect/class-stats) ;; 已加载 class 总数
(repl.inspect/search-classes "^com.example") ;; 正则搜已加载 class
(repl.inspect/class-detail "com.example.App") ;; 方法/字段/classloader 详情
(repl.inspect/object-size some-obj) ;; 对象浅大小(字节)
(repl.inspect/heap-dump! "/tmp/dump.hprof") ;; 堆 dump(默认只留存活对象)
;; dashboard 用:一次拿全部
(repl.inspect/snapshot)
;; => {:at ... :memory {...} :gc [...] :threads {...} :system {...}
;; :uptime-ms ... :classes {:loaded-count 8285}}在需要使用的项目中完成下面操作
- maven
直接在项目的
pom.xml里引入依赖 记得刷新maven
<dependency>
<groupId>io.github.schneiderlin</groupId>
<artifactId>nrepl-starter</artifactId>
<version>2.0.0-SNAPSHOT</version>
</dependency>- gradle 记得刷新gradle
implementation 'io.github.schneiderlin:nrepl-starter:1.0.9'使用 Java Agent 可以自动启动 nrepl server,无需修改 main 函数。Agent 会在 JVM 启动时自动运行,无需任何代码修改。
首先需要构建项目以生成 agent jar:
mvn clean package构建完成后,在 target 目录下会生成 nrepl-starter-2.0.0-SNAPSHOT-agent.jar 文件(包含所有依赖的 fat jar)。
在启动应用时添加 -javaagent 参数:
java -javaagent:./target/nrepl-starter-2.0.0-SNAPSHOT-agent.jar -jar your-app.jar或者在 IDE 中配置 VM options (Run Configuration):
-javaagent:path/to/nrepl-starter-2.0.0-SNAPSHOT-agent.jar
如果 agent jar 已发布到 Maven 仓库,可以通过 Maven 插件下载:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-agent</id>
<phase>package</phase>
<goals>
<goal>copy</goal>
</goals>
<configuration>
<artifactItems>
<artifactItem>
<groupId>io.github.schneiderlin</groupId>
<artifactId>nrepl-starter</artifactId>
<version>2.0.0-SNAPSHOT</version>
<classifier>agent</classifier>
<type>jar</type>
</artifactItem>
</artifactItems>
<outputDirectory>${project.build.directory}</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>通过系统属性配置 agent:
-Dnrepl.port=7888- 设置 nrepl 端口 (默认: 7888)-Dnrepl.enabled=true- 启用/禁用 nrepl (默认: true)
示例:
java -javaagent:./nrepl-starter-2.0.0-SNAPSHOT-agent.jar \
-Dnrepl.port=9999 \
-Dnrepl.enabled=true \
-jar your-app.jar public static void main(String[] args) {
// ----------- 添加 nrepl server --------------------
R r = new R(new StarterServiceProperties());
Thread replThread = r.start(7888);
System.out.println("Hello World!");
Thread workerThread = new Thread(() -> {
// This thread will run indefinitely or until its task is complete
while (true) {
try {
Thread.sleep(1000); // Simulate work
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // Restore interrupt status
break;
}
}
});
workerThread.start();
// --------------------------------------------------
// 其他业务代码
}项目包含一个完整的示例项目 (examples/basic),演示如何使用 nrepl-starter。
首先需要构建主项目(因为示例依赖 SNAPSHOT 版本):
# 在项目根目录执行
mvn clean install然后进入示例项目目录:
cd examples/basic
mvn clean package示例项目提供了两种运行方式:
使用 agent 方式无需修改代码。首先需要确保主项目已构建并生成了 agent jar:
# 在项目根目录执行
mvn clean package然后运行示例项目:
推荐: 使用 IDE 运行
- 在 IDE (IntelliJ IDEA / Eclipse) 中打开
examples/basic项目 - 配置 Run Configuration:
- Main class:
com.example.App - VM options:
-javaagent:../../target/nrepl-starter-2.0.0-SNAPSHOT-agent.jar
- Main class:
- 运行即可
或者使用命令行 (使用 java -jar):
cd examples/basic
# 1. 先构建项目 (生成可执行 jar)
mvn clean package
# 2. 使用手动方式运行 (使用 App.java 中已有的代码)
java -jar target/basic-1.0-SNAPSHOT.jar
# 3. 使用 agent 方式运行:
# 先注释掉 App.java 中第 19-21 行的手动启动代码,然后重新打包
mvn clean package
java -javaagent:../../target/nrepl-starter-2.0.0-SNAPSHOT-agent.jar -jar target/basic-1.0-SNAPSHOT.jar注意: 如果使用 agent 方式,需要先注释掉 App.java 中手动启动 nrepl 的代码(第 19-21 行),避免重复启动。
示例项目的 App.java 已经包含了手动启动 nrepl 的代码,可以直接运行:
# 从 examples/basic 目录运行
mvn clean package
java -jar target/basic-1.0-SNAPSHOT.jar或者在 IDE 中直接运行 com.example.App 的 main 方法。
启动应用后,nrepl server 会在 7888 端口监听。
- 打开 VSCode,安装 Calva 插件
- 打开
examples/basic目录 - 使用命令面板 (Ctrl+Shift+P / Cmd+Shift+P) 选择 "Calva: Connect to a Running REPL Server"
- Project type 选择
generic - 输入端口号
7888 - 连接成功后,可以在 REPL 中调用 Java 方法
连接成功后,可以在 REPL 中执行以下代码(参考 src/repl/core.clj):
;; 导入 Java 类
(ns repl.core
(:import [com.example App]))
;; 调用静态方法
(App/add 1 2)
;; => 3
;; 创建实例
(def app (App.))
;; 调用实例方法
(.subtract app 10 3)
;; => 7examples/basic/
├── pom.xml # Maven 配置,包含 nrepl-starter 依赖
├── src/
│ ├── main/
│ │ └── java/
│ │ └── com/example/
│ │ └── App.java # 示例 Java 类,包含可调用的方法
│ └── repl/
│ └── core.clj # Clojure REPL 示例代码
本地安装 clojure
https://clojure.org/guides/install_clojure
安装 vscode, 和 vscode 插件 calva
启动 spring 项目, PlutusApplication.main.
启动完成后, 7888 端口会有一个 clojure nrepl 进程在监听.
在 vscode 使用 calva 插件的 connect to a running REPL server

project type 选择 generic

输入端口号 7888
