1. 简介
在本教程中,我们将介绍 Picocli 这个库,它可以帮助我们快速构建 Java 命令行程序。
我们会从一个简单的 Hello World 命令开始,然后深入 Picocli 的关键特性,通过模拟实现部分 git
命令的功能来展示其强大之处。
2. Hello World 命令
我们先从一个简单的 Hello World 示例开始。
首先,确保在 pom.xml
中添加 Picocli 依赖:
<dependency>
<groupId>info.picocli</groupId>
<artifactId>picocli</artifactId>
<version>4.7.0</version>
</dependency>
接着,创建一个类并使用 @Command
注解来定义命令:
@Command(
name = "hello",
description = "输出 Hello World"
)
public class HelloWorldCommand implements Runnable {
public static void main(String[] args) {
CommandLine.run(new HelloWorldCommand(), args);
}
@Override
public void run() {
System.out.println("Hello World!");
}
}
运行 main
方法后,控制台将输出:
Hello World!
如果你打包成 jar 文件,也可以通过如下命令运行:
java -cp "picocli.jar;your-command.jar" com.example.HelloWorldCommand
3. 实战案例:模拟 Git 命令
接下来,我们将用 Picocli 实现一个类似 git
的命令结构,包括子命令、选项和参数。
首先,创建主命令类 GitCommand
:
@Command
public class GitCommand implements Runnable {
public static void main(String[] args) {
CommandLine.run(new GitCommand(), args);
}
@Override
public void run() {
System.out.println("这是模拟的 git 命令");
}
}
4. 添加子命令
Git 有多个子命令,如 add
、commit
等。Picocli 提供了三种方式来注册子命令。
4.1 使用 @Command
注解在类上
@Command(
name = "git",
subcommands = {GitAddCommand.class, GitCommitCommand.class}
)
public class GitCommand implements Runnable {
// ...
}
子命令类示例:
@Command(name = "add")
public class GitAddCommand implements Runnable {
@Override
public void run() {
System.out.println("添加文件到暂存区");
}
}
@Command(name = "commit")
public class GitCommitCommand implements Runnable {
@Override
public void run() {
System.out.println("提交暂存区内容");
}
}
运行命令:
git add
输出:
添加文件到暂存区
4.2 使用 @Command
注解在方法上
你也可以在主类中定义方法并用 @Command
注解:
@Command(name = "add")
public void add() {
System.out.println("添加文件到暂存区");
}
@Command(name = "commit")
public void commit() {
System.out.println("提交暂存区内容");
}
这种方式更简洁,适合逻辑简单的命令。
4.3 编程方式添加子命令
适用于需要动态注册子命令的场景:
CommandLine commandLine = new CommandLine(new GitCommand());
commandLine.addSubcommand("add", new GitAddCommand());
commandLine.addSubcommand("commit", new GitCommitCommand());
commandLine.parseWithHandler(new CommandLine.RunLast(), args);
注意:此时不能使用 CommandLine.run()
,需要手动调用 parseWithHandler()
。
5. 使用 @Option
注解管理选项
5.1 无参数选项
例如,添加 -A
或 --all
选项来表示添加所有文件:
@Option(names = {"-A", "--all"})
private boolean all;
@Override
public void run() {
if (all) {
System.out.println("添加所有文件到暂存区");
} else {
System.out.println("添加部分文件到暂存区");
}
}
5.2 有参数选项
比如 commit
命令的 -m
消息参数:
@Option(names = {"-m", "--message"})
private String message;
@Override
public void run() {
if (message != null) {
System.out.println("提交信息: " + message);
}
}
5.3 多参数选项
支持多次使用 -m
:
@Option(names = {"-m", "--message"})
private String[] messages;
@Override
public void run() {
for (String msg : messages) {
System.out.println("提交信息: " + msg);
}
}
也可以使用分隔符一次性输入:
@Option(names = {"-m", "--message"}, split = ",")
private String[] messages;
使用方式:
git commit -m "first msg,second msg"
5.4 必填选项
标记某个选项为必填:
@Option(names = {"-m", "--message"}, required = true)
private String[] messages;
如果未提供 -m
,Picocli 会输出错误信息并显示帮助。
6. 使用 @Parameters
管理位置参数
6.1 捕获位置参数
比如 add file1 file2
:
@Parameters
private List<Path> files;
@Override
public void run() {
files.forEach(file -> System.out.println("添加文件: " + file));
}
6.2 捕获指定位置参数
使用 index
参数选择特定位置:
@Parameters(index = "2..*")
private List<Path> files;
这将捕获第 3 个及之后的所有参数。
7. 类型转换机制
Picocli 内置支持多种类型转换,例如 Path
、Integer
、Enum
等。
自定义类型转换
比如我们定义一个配置项枚举:
public enum ConfigElement {
USERNAME("user.name"), EMAIL("user.email");
private final String value;
ConfigElement(String value) {
this.value = value;
}
public String value() {
return value;
}
public static ConfigElement from(String value) {
return Arrays.stream(values())
.filter(e -> e.value.equals(value))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("无效配置项: " + value));
}
}
在命令中使用:
@Parameters(index = "0")
private ConfigElement element;
@Parameters(index = "1")
private String value;
@Override
public void run() {
System.out.println("设置 " + element.value() + " 为 " + value);
}
注册转换器:
CommandLine cmd = new CommandLine(new GitCommand());
cmd.registerConverter(ConfigElement.class, ConfigElement::from);
cmd.parseWithHandler(new RunLast(), args);
当传入非法参数时,会提示错误并显示帮助信息。
8. 与 Spring Boot 集成
如果你想在 Spring Boot 项目中使用 Picocli,可以这样做:
定义 Spring Boot 主类:
@SpringBootApplication
public class Application implements CommandLineRunner {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
private final GitCommand gitCommand;
private final GitAddCommand addCommand;
private final GitCommitCommand commitCommand;
private final GitConfigCommand configCommand;
public Application(GitCommand gitCommand, GitAddCommand addCommand,
GitCommitCommand commitCommand, GitConfigCommand configCommand) {
this.gitCommand = gitCommand;
this.addCommand = addCommand;
this.commitCommand = commitCommand;
this.configCommand = configCommand;
}
@Override
public void run(String... args) {
CommandLine cmd = new CommandLine(gitCommand);
cmd.addSubcommand("add", addCommand);
cmd.addSubcommand("commit", commitCommand);
cmd.addSubcommand("config", configCommand);
cmd.parseWithHandler(new CommandLine.RunLast(), args);
}
}
同时,确保你的命令类标注 @Component
,以便 Spring 扫描到它们。
这样,你就可以在命令类中注入 Spring Bean,实现依赖注入。
9. 总结 ✅
本文介绍了 Picocli 的核心功能:
- 创建基本命令
- 添加子命令(类、方法、编程方式)
- 使用
@Option
定义各种选项 - 使用
@Parameters
捕获位置参数 - 自定义类型转换
- 与 Spring Boot 集成
Picocli 是一个非常强大的命令行解析库,支持自动帮助生成、嵌套命令、类型转换、自定义解析逻辑等。它的文档非常完整,建议进一步阅读:Picocli 官方文档。
如果你正在开发需要复杂 CLI 的 Java 项目,Picocli 绝对是一个值得考虑的选项。