一个简单,轻量,开箱即用,注解驱动,常驻交互的Java终端库:Simple-Terminal
注解驱动、开箱即用、常驻交互。不是再写一个参数解析器,而是给你的程序装上一整套 REPL——还能接网络流做远程终端。
先说痛点:这些场景,你还在自己造轮子吗?
如果你
程序跑起来了,想随时敲命令干预它 游戏服务器、机器人、守护进程……它们都是常驻运行的。你想查个在线人数、踢个人、热重载配置,结果发现现有的命令行库解析完 main(String[] args) 就退出了——它们是为「敲一下就跑」的一次性工具设计的,不是为常驻控制台设计的。想做 REPL?那得自己写 while 循环、自己拆分输入、自己分发命令、自己写 help。你烦了,希望有一个简单,易用,开箱即用,交互式常驻的库。
想做一个分步交互的命令 比如「先输入消息内容,再问发给谁,再问优先级」。这种多轮对话,如果自己维护一个状态机一写就是一大坨 if-else。你希望有一个库能帮你解决这些难点
想远程连上去敲命令 程序部署在服务器上,你不想为了改个配置就开 HTTP 接口、写鉴权、写文档。你想像 SSH 那样,开个 netcat 连上去就能敲命令。可现有的方案要么只读一行不管后续,要么输入输出流写死了 System.out,接不到网络上。你希望这个库能够自行设置输入输出流。
参数校验写到吐 端口是不是数字?用户名符不符合规范?日期格式对不对?每个命令都要写一堆 if 判断,还要 try-catch 转类型。一个命令十个参数,校验代码比业务代码还长。你希望这个库能支持正则表达式,提前为你检查参数格式是否正确。
退出时希望优雅地关闭资源 输入stop命令,你想在退出前做点收尾,希望有一个统一的钩子。
免费开源商用 你想白嫖,库最好是 MIT 开源的。
这些痛点,Simple-Terminal 全部解决了。下面一个个说。
Simple-Terminal 是什么
Simple-Terminal 是一个轻量的 Java 终端库。最少4行代码启动一个常驻控制台,自带 help 和 stop;用注解注册命令,自动转换参数类型,用正则校验参数,支持子命令、多轮对话式命令、上下文跳转、国际化、异步执行,还能接入网络流做远程终端。
Terminal terminal = TerminalBuilder.builder()
.setAnchorPackageClassHelper(YourCommandProcessor.class)
.build();
terminal.run();
跑起来就是这样一个交互界面:
> info CTimet
Hello, CTimet!
> stop
...
突出优势
1. 常驻交互,开箱即用
这是 Simple-Terminal 最根本的定位。它天生就是常驻的——你不用写循环,不用管分发,run() 一调用就是一个完整的 REPL。自带 help(列出所有命令、查看用法)和 stop(退出进程)。 help 国际化也不需要自己写,你只需要提供命令解释的国际化资源,Simple-Terminal的help命令就能自动根据当前语言区域,自动读取对应的国际化properties文件。
适合场景:任何需要常驻运行、随时接受指令的程序——游戏服务器、聊天机器人、定时任务守护进程、IoT 设备网关、开发调试控制台。
2. 多轮对话 + 上下文跳转
想象一个 send 命令:先问发什么,再问发给谁,再问感受评分。用 Simple-Terminal 只需给每个方法标上 stage:
@CommandProcessor(value = "send", hasContext = true)
public class SendContextCommandProcessor {
@InjectOutPrintStream
private PrintStream out;
@CommandExecutor
public void send(String content) {
out.println("Send '" + content + "' to whom?");
}
@ContextCommandExecutor(stage = 1, paraJoin = true)
public void acceptUser(String user) {
out.println("Send to '" + user + "' done. Your feeling rate:");
}
@ContextCommandExecutor(stage = 2)
public void acceptUserFeelingRate(int rate) {
out.println("Rate = " + rate);
}
// ...还能继续 stage 3、4
}
交互过程:
> send hello
Send 'hello' to whom?
> CTimet bukii
Send to 'CTimet bukii' done. Your feeling rate:
> 8
Rate = 8
更强大的是上下文跳转——让方法返回一个 int,下一轮输入就会跳到对应的 stage。「有密码就要求输入,没密码就跳过」这种分支流程,一行 return 搞定:
@ContextCommandExecutor(stage = 2, patterns = "^[1-4]\\d*$")
public int jumpTo(int jumpTo) {
out.println("Jump to " + jumpTo + ".");
return jumpTo; // 下一轮输入进入 jumpTo 这个 stage
}
还有两个配套控制:
- 重复当前轮:抛
NeedRepeatException,用户重新输入这一轮(密码错了重来) - 提前终止:抛
ContextInterruptException,命令直接退出(密码错了不想继续后面步骤)
适合场景:安装向导、配置流程、数据库迁移确认、多步表单、交互式问答、任何需要分步收集信息或分支处理的命令。以前这些要写一坨状态机,现在几个注解就搞定。
3. 接入网络流,把终端变成远程控制台
这是 Simple-Terminal 最被低估的能力。输入输出流都可以替换——这意味着你可以把终端接到 Socket 上,做一个远程控制台:
Socket socket = server.accept();
Terminal terminal = TerminalBuilder.builder()
.setAnchorPackageClassHelper(App.class)
.setTerminalInputStream(socket.getInputStream())
.setTerminalOutputStream(new PrintStream(socket.getOutputStream()))
.runAsync() // 异步监听,主线程继续接受下一个连接
.build();
terminal.run();
配合 @InjectOutPrintStream 注入输出流,处理器里写 out.println(...) 就会发到网络另一端,不用写死 System.out。运维同学开个 netcat 连上来就能敲命令,比开 HTTP 接口轻量得多。
适合场景:远程运维控制台、游戏服务器后台管理、嵌入式设备调试、分布式节点控制、多客户端同时连接的交互式服务。
4. 正则校验 + 自动重载分发,把 90% 的 if 交给框架
写命令行最烦的是校验参数。Simple-Terminal 用 patterns 正则数组帮你校验,更妙的是同一个命令名可以注册多个重载方法,框架会根据用户输入的类型自动匹配:
@CommandProcessor("hello")
public class HelloCommandProcessor {
@InjectOutPrintStream
PrintStream out;
@CommandExecutor
public void hello() { out.println("hello"); } // hello
@CommandExecutor
public void helloUser(String user) { out.println("hello " + user); } // hello a
@CommandExecutor
public void helloId(int id) { out.println(id); } // hello 1
@CommandExecutor(patterns = {"[0-9]", "[0-9]"})
public void helloId(int id1, int id2) { out.println(id1); out.println(id2); } // hello 1 2
}
输入 hello 走无参版,hello a 走 String 版,hello 1 走 int 版,hello 1 2 走双 int 版。参数类型和正则都由框架比对,匹配不上就自动走 @ArgsIllegalExecutor 兜底。你只管写业务逻辑,校验和分发交给 Simple-Terminal。
适合场景:任何需要严格参数校验的命令——端口配置、日期输入、用户名规范、ID 校验、多参数组合命令。
5. 四种参数转换策略 + 自定义布尔标记
底层用 ElegantConvertChecker 把字符串转成方法参数类型,提供四种策略:
| 策略 | 行为 |
|---|---|
CONVERT |
严格转换,"abc" 喂给 int 直接拒绝 |
COERCE |
强制转换,"1.1111111111111111" 喂给 float 会变成 1.1112 |
INTERPRET |
语义解释后转换 |
COERCE_WITH_INTERPRET |
默认策略,先解释后强制 |
更实用的是自定义布尔标记:Y/y/T/t/1/是/yes,任何你想要的字符串都能被转成 true。做确认类命令(delete -confirm yes)时特别顺手,不用自己写 if (input.equals("yes") || input.equals("y"))。
适合场景:确认类命令、容忍宽松输入的配置命令、需要精确控制「什么算合法」的严格命令。
6. 优雅退出 + 定时关停
退出时 Simple-Terminal 会先调用所有 ClosableFlag 处理器的 closeResources(),再执行你配置的 stopRunnable,最后才 System.exit。数据库连接、文件句柄、网络 socket——都能被妥善关闭:
@CommandProcessor("db")
public class DbCommandProcessor implements ClosableFlag {
private Connection conn;
@Override
public void closeResources() throws Exception {
if (conn != null) conn.close();
}
}
而且 stop 命令自带一整套定时退出:
stop -ms 200 # 200 毫秒后
stop -s 2 # 2 秒后
stop -min 2 # 2 分钟后
stop -h 2 # 2 小时后
stop -r 2030 2 17 13 14 15 888 # 定时到 2030-02-17 13:14:15.888
适合场景:凌晨维护、定时重启、预约关停、任何需要在退出前释放资源的常驻服务。
还有这些顺手的设计
- 国际化:根据系统语言自动加载
descriptions_zh.properties/descriptions.properties,help 文案中英文自动切换。descriptionKey机制让你只管提供资源文件,help 命令自动查表展示。 - 子命令:
@SubcommandExecutor("at")注册子命令,支持alias别名和matchAllCases大小写策略。子命令不必须以-开头,任何不含空格的字符串都行。 - 命令别名:
alias = {"hi", "hello"},想怎么叫就怎么叫。 - 字段注入:
@InjectOutPrintStream、@InjectErrPrintStream、@InjectTerminalOption、@InjectCommandDescriptionProperties——运行时对象自动注入,不用层层传参,也避免写死System.out。 - 异步执行:
runAsync()让终端在独立线程监听,主线程继续干别的事;也可以塞进自定义线程池。 - Java 8 兼容:核心模块编译目标 1.8,老项目也能用。
使用场景速查
| 你的需求 | 用 Simple-Terminal 的什么 |
|---|---|
| 给常驻服务加运维控制台 | Terminal.run() 常驻 REPL + 内置 help/stop |
| 远程连上去敲命令 | setTerminalInputStream/OutputStream 接网络流 |
| 分步交互的向导命令 | @ContextCommandExecutor + stage |
| 分支式多轮流程 | 上下文跳转(返回 int) |
| 密码错了重来 | NeedRepeatException |
| 中途放弃命令 | ContextInterruptException |
| 端口/日期等参数校验 | patterns 正则 |
| 同命令不同参数自动分发 | 方法重载 + 自动匹配 |
| 确认类命令(yes/no) | 自定义布尔标记 |
| 退出前释放资源 | ClosableFlag |
| 定时/预约关停 | stop -s/-min/-h/-r |
| 中英文 help 文档 | i18n properties + descriptionKey |
| 一个命令多个名字 | alias |
上手
Maven:
<dependency>
<groupId>io.github.ctimetbukii</groupId>
<artifactId>simple-terminal</artifactId>
<version>1.0.0</version>
</dependency>
Terminal terminal = TerminalBuilder.builder()
.setAnchorPackageClassHelper(App.class)
.build();
terminal.run();
打上 @CommandProcessor,写个 @CommandExecutor 方法,你的命令就注册好了。完整文档见 项目README。
写在最后
Java 生态里,「一次性参数解析」已经很成熟了,但「常驻交互式终端」这一层长期空白——要么自己拿底层库糊一个,要么拿参数解析器套层循环。Simple-Terminal 想把这件事变得无聊地简单:注解一打,终端就有了;正则一写,校验就做了;流一接,远程控制台就成了。
1.0.0 是第一个正式版本,API 已经稳定。项目基于 MIT 协议开源,欢迎 Star、Issue、PR。如果你正在维护一个需要运维控制台的 Java 服务,不妨试试,两行代码就能跑起来。
适用边界
Simple-Terminal 专注的是「常驻交互式终端」,不是所有命令行场景都适合。以下情况,传统的参数解析器依然是更合适的选择:
- 一次性命令行工具:像
git、mvn这种「敲一下就跑、跑完就退」的工具。这类工具不需要常驻 REPL,用传统的一次性参数解析器更轻量、更对口。 - 复杂的 POSIX 风格选项组合:如果你需要
--flag=value、-abc(等价于-a -b -c)、--分隔符、选项互斥、必选选项校验这类复杂的 POSIX/GNU 风格选项解析,传统参数解析器在这块做得更成熟、更规范。 - 命令行自动补全脚本生成:如果你需要生成 bash/zsh 的自动补全脚本,这是传统参数解析器的强项,Simple-Terminal 目前不提供这个能力。
需要常驻交互、多轮对话、远程控制台——用 Simple-Terminal;需要一次性执行、复杂选项解析、补全脚本——用传统参数解析器。 两者各有所长,按需选择就好。
求 Star 喵
如果你觉得 Simple-Terminal 好用/易用/解决了你的痛点/需求,不仿给个Star吧!求求你了喵⭐
项目地址:https://github.com/CTimetbukii/Simple-Termina
MIT 开源
真的不给个 Star⭐ 吗喵?>︿<
posted on 2026-07-14 14:24 CTimet_bukii 阅读(1) 评论(0) 收藏 举报
浙公网安备 33010602011771号