简介
picocli 是 java 生态里很常用的命令行工具开发库。
它主要解决这些问题:
解析命令行参数 处理短选项和长选项 支持位置参数 自动做类型转换 生成帮助文档 生成版本信息 支持子命令 支持命令补全 支持打包成可执行 jar 支持 graalvm native image
一句话概括:
picocli 用注解描述命令、选项和参数,把 string[] args 解析成 java 对象。
没有 picocli 时,命令行参数通常要手动解析:
public static void main(string[] args) {
for (int i = 0; i < args.length; i++) {
if ("--file".equals(args[i])) {
string file = args[++i];
system.out.println(file);
}
}
}
这种写法很快会变乱:
--file a.txt--file=a.txt-f a.txt-abc- 必填参数
- 默认值
- 参数类型转换
- 参数错误提示
--help- 子命令
picocli 把这些通用能力封装好了。
picocli 适合什么场景
常见场景如下:
| 场景 | 示例 |
|---|---|
| 开发者工具 | 代码生成、项目初始化、接口调试 |
| 运维工具 | 发布、备份、清理、巡检 |
| 数据处理工具 | csv 转换、日志分析、批量导入 |
| 管理脚本 | 用户管理、配置检查、任务触发 |
| spring boot cli | 复用 service 做命令行管理工具 |
| 本地小工具 | 文件重命名、图片处理、目录扫描 |
它和一些常见 cli 工具的定位类似:
| 语言 | 常见 cli 框架 |
|---|---|
| java | picocli |
| go | cobra |
| python | argparse / click / typer |
| node.js | commander |
| .net | system.commandline |
maven 依赖
当前 picocli 最新稳定版本是 4.7.7。
maven:
<dependency>
<groupid>info.picocli</groupid>
<artifactid>picocli</artifactid>
<version>4.7.7</version>
</dependency>gradle kotlin dsl:
dependencies {
implementation("info.picocli:picocli:4.7.7")
}
如果要生成 graalvm native image 配置、命令补全脚本等,可以再加 picocli-codegen。
<dependency>
<groupid>info.picocli</groupid>
<artifactid>picocli-codegen</artifactid>
<version>4.7.7</version>
<scope>provided</scope>
</dependency>核心注解
picocli 最常用的注解不多。
| 注解 | 作用 |
|---|---|
@command | 定义一个命令 |
@option | 定义选项,比如 -f、--file |
@parameters | 定义位置参数,比如 copy a.txt b.txt 里的两个文件 |
@mixin | 复用一组公共选项 |
@parentcommand | 在子命令里拿到父命令对象 |
@spec | 拿到当前命令的元数据 |
一个命令类通常实现 runnable 或 callable<integer>。
runnable:只执行逻辑,不关心返回值 callable<integer>:可以返回退出码,更适合正式 cli
正式工具更推荐 callable<integer>。
退出码约定:
0:成功 非 0:失败
第一个 demo:hello 命令
先写一个最简单的命令。
目标:
java -jar hello.jar 张三
输出:
hello, 张三
代码:
package com.example.picoclidemo;
import picocli.commandline;
import picocli.commandline.command;
import picocli.commandline.parameters;
import java.util.concurrent.callable;
@command(
name = "hello",
description = "打印问候语",
mixinstandardhelpoptions = true,
version = "hello 1.0.0"
)
public class hellocommand implements callable<integer> {
@parameters(index = "0", description = "名称")
private string name;
@override
public integer call() {
system.out.println("hello, " + name);
return 0;
}
public static void main(string[] args) {
int exitcode = new commandline(new hellocommand()).execute(args);
system.exit(exitcode);
}
}运行:
java com.example.picoclidemo.hellocommand 张三
输出:
hello, 张三
查看帮助:
java com.example.picoclidemo.hellocommand --help
输出类似:
usage: hello [-hv] <name>
打印问候语
<name> 名称
-h, --help show this help message and exit.
-v, --version print version information and exit.mixinstandardhelpoptions = true 会自动添加:
-h, --help -v, --version
这两个选项几乎每个正式 cli 都应该有。
option 和 parameters 的区别
命令行参数大致分两类。
option
带名字的参数叫选项。
比如:
tool --file app.log --limit 100 --verbose
对应 picocli:
@option(names = {"-f", "--file"}, description = "文件路径")
private file file;
@option(names = {"-l", "--limit"}, defaultvalue = "100", description = "最多处理多少行")
private int limit;
@option(names = {"-v", "--verbose"}, description = "输出详细日志")
private boolean verbose;parameters
没有名字、靠位置识别的参数叫位置参数。
比如:
copy source.txt target.txt
对应 picocli:
@parameters(index = "0", description = "源文件") private file source; @parameters(index = "1", description = "目标文件") private file target;
简单判断:
有 - 或 -- 前缀:@option 没有前缀,靠顺序:@parameters
实战 demo:文件统计工具
下面做一个稍微实用点的工具:统计文本文件。
支持功能:
统计行数 统计字符数 忽略空行 限制最多读取多少行 输出详细信息
命令示例:
textstat readme.md --chars --ignore-blank --limit 1000 -v
命令类
package com.example.picoclidemo;
import picocli.commandline;
import picocli.commandline.command;
import picocli.commandline.option;
import picocli.commandline.parameters;
import java.io.ioexception;
import java.nio.charset.standardcharsets;
import java.nio.file.files;
import java.nio.file.path;
import java.util.list;
import java.util.concurrent.callable;
@command(
name = "textstat",
description = "统计文本文件的行数和字符数",
mixinstandardhelpoptions = true,
version = "textstat 1.0.0"
)
public class textstatcommand implements callable<integer> {
@parameters(index = "0", description = "要统计的文本文件")
private path file;
@option(names = {"-c", "--chars"}, description = "统计字符数")
private boolean countchars;
@option(names = {"--ignore-blank"}, description = "忽略空行")
private boolean ignoreblank;
@option(names = {"-l", "--limit"}, defaultvalue = "0", description = "最多读取多少行,0 表示不限制")
private int limit;
@option(names = {"-v", "--verbose"}, description = "输出详细信息")
private boolean verbose;
@override
public integer call() {
if (!files.exists(file)) {
system.err.println("文件不存在: " + file);
return 2;
}
if (!files.isregularfile(file)) {
system.err.println("不是普通文件: " + file);
return 2;
}
try {
list<string> lines = files.readalllines(file, standardcharsets.utf_8);
if (limit > 0 && lines.size() > limit) {
lines = lines.sublist(0, limit);
}
long linecount = lines.stream()
.filter(line -> !ignoreblank || !line.isblank())
.count();
system.out.println("lines=" + linecount);
if (countchars) {
int chars = lines.stream()
.filter(line -> !ignoreblank || !line.isblank())
.maptoint(string::length)
.sum();
system.out.println("chars=" + chars);
}
if (verbose) {
system.out.println("file=" + file.toabsolutepath());
system.out.println("limit=" + limit);
system.out.println("ignoreblank=" + ignoreblank);
}
return 0;
} catch (ioexception e) {
system.err.println("读取文件失败: " + e.getmessage());
return 1;
}
}
public static void main(string[] args) {
int exitcode = new commandline(new textstatcommand()).execute(args);
system.exit(exitcode);
}
}运行:
java com.example.picoclidemo.textstatcommand readme.md
输出:
lines=128
统计字符数:
java com.example.picoclidemo.textstatcommand readme.md --chars
输出:
lines=128 chars=5042
忽略空行并输出详细信息:
java com.example.picoclidemo.textstatcommand readme.md --ignore-blank -v
输出:
lines=96 file=/users/test/project/readme.md limit=0 ignoreblank=true
缺少文件参数:
java com.example.picoclidemo.textstatcommand
picocli 会直接提示参数缺失,并打印用法。
常用 option 写法
必填选项
@option(names = {"-u", "--username"}, required = true, description = "用户名")
private string username;
没传会报错:
missing required option: '--username=<username>'
默认值
@option(names = {"-p", "--port"}, defaultvalue = "8080", description = "端口,默认 ${default-value}")
private int port;
帮助信息里可以用:
${default-value}
显示默认值。
布尔开关
@option(names = {"-v", "--verbose"}, description = "详细输出")
private boolean verbose;
只要命令里出现 -v,值就是 true。
tool -v tool --verbose
多值参数
@option(names = {"-t", "--tag"}, description = "标签,可重复")
private list<string> tags;
运行:
tool -t java -t cli -t demo
结果:
[java, cli, demo]
也可以用 split:
@option(names = "--tags", split = ",", description = "逗号分隔的标签") private list<string> tags;
运行:
tool --tags java,cli,demo
可选值
有些选项传不传值都可以。
比如:
tool --color tool --color=always tool --color=never
写法:
@option(
names = "--color",
arity = "0..1",
fallbackvalue = "always",
defaultvalue = "auto",
description = "颜色模式: auto, always, never"
)
private string color;
含义:
不传 --color:auto 只传 --color:always 传 --color=never:never
枚举参数
enum outputformat {
text, json, csv
}
@option(names = {"-f", "--format"}, defaultvalue = "text", description = "输出格式: ${completion-candidates}")
private outputformat format;completion-candidates 会显示候选值。
运行:
tool --format json
位置参数
单个位置参数:
@parameters(index = "0", description = "输入文件") private path input;
多个位置参数:
@parameters(index = "0", description = "源文件") private path source; @parameters(index = "1", description = "目标文件") private path target;
剩余参数:
@parameters(index = "0..*", description = "文件列表") private list<path> files;
运行:
tool a.txt b.txt c.txt
类型转换
picocli 内置了很多常见类型转换。
常见类型:
| java 类型 | 命令行传值 |
|---|---|
string | hello |
int / integer | 8080 |
long / long | 10000 |
boolean | true / false,或布尔开关 |
file | ./a.txt |
path | ./a.txt |
url | https://example.com |
uri | file:///tmp/a.txt |
duration | pt10s |
localdate | 2026-01-01 |
enum | json |
如果内置转换不够,可以写自定义转换器。
自定义 localdate 转换器
假设日期想支持:
2026-07-18
转换器:
package com.example.picoclidemo;
import picocli.commandline.itypeconverter;
import java.time.localdate;
import java.time.format.datetimeformatter;
public class localdateconverter implements itypeconverter<localdate> {
@override
public localdate convert(string value) {
return localdate.parse(value, datetimeformatter.iso_local_date);
}
}使用:
@option(names = "--date", converter = localdateconverter.class, description = "日期,格式 yyyy-mm-dd") private localdate date;
运行:
tool --date 2026-07-18
子命令 demo:filecli
复杂 cli 一般不是一个命令解决所有问题,而是类似:
git add git commit kubectl get pods docker image ls
picocli 支持子命令。
下面做一个 filecli:
filecli stat readme.md filecli copy a.txt b.txt --force filecli delete temp.log --dry-run
主命令
package com.example.picoclidemo.filecli;
import picocli.commandline;
import picocli.commandline.command;
@command(
name = "filecli",
description = "文件处理命令行工具",
mixinstandardhelpoptions = true,
version = "filecli 1.0.0",
subcommands = {
statcommand.class,
copycommand.class,
deletecommand.class
}
)
public class fileclicommand implements runnable {
@override
public void run() {
commandline.usage(this, system.out);
}
public static void main(string[] args) {
int exitcode = new commandline(new fileclicommand()).execute(args);
system.exit(exitcode);
}
}stat 子命令
package com.example.picoclidemo.filecli;
import picocli.commandline.command;
import picocli.commandline.parameters;
import java.nio.file.files;
import java.nio.file.path;
import java.util.concurrent.callable;
@command(name = "stat", description = "查看文件基本信息")
public class statcommand implements callable<integer> {
@parameters(index = "0", description = "文件路径")
private path file;
@override
public integer call() throws exception {
if (!files.exists(file)) {
system.err.println("文件不存在: " + file);
return 2;
}
system.out.println("path=" + file.toabsolutepath());
system.out.println("size=" + files.size(file));
system.out.println("directory=" + files.isdirectory(file));
system.out.println("regularfile=" + files.isregularfile(file));
return 0;
}
}copy 子命令
package com.example.picoclidemo.filecli;
import picocli.commandline.command;
import picocli.commandline.option;
import picocli.commandline.parameters;
import java.nio.file.files;
import java.nio.file.path;
import java.nio.file.standardcopyoption;
import java.util.concurrent.callable;
@command(name = "copy", description = "复制文件")
public class copycommand implements callable<integer> {
@parameters(index = "0", description = "源文件")
private path source;
@parameters(index = "1", description = "目标文件")
private path target;
@option(names = {"-f", "--force"}, description = "覆盖已存在文件")
private boolean force;
@override
public integer call() throws exception {
if (!files.exists(source)) {
system.err.println("源文件不存在: " + source);
return 2;
}
if (files.exists(target) && !force) {
system.err.println("目标文件已存在,使用 --force 覆盖: " + target);
return 3;
}
if (force) {
files.copy(source, target, standardcopyoption.replace_existing);
} else {
files.copy(source, target);
}
system.out.println("复制完成: " + source + " -> " + target);
return 0;
}
}delete 子命令
package com.example.picoclidemo.filecli;
import picocli.commandline.command;
import picocli.commandline.option;
import picocli.commandline.parameters;
import java.nio.file.files;
import java.nio.file.path;
import java.util.concurrent.callable;
@command(name = "delete", description = "删除文件")
public class deletecommand implements callable<integer> {
@parameters(index = "0", description = "文件路径")
private path file;
@option(names = "--dry-run", description = "只打印将要执行的操作,不真正删除")
private boolean dryrun;
@override
public integer call() throws exception {
if (!files.exists(file)) {
system.err.println("文件不存在: " + file);
return 2;
}
if (dryrun) {
system.out.println("[dry-run] 将删除: " + file.toabsolutepath());
return 0;
}
files.delete(file);
system.out.println("删除完成: " + file.toabsolutepath());
return 0;
}
}运行:
java com.example.picoclidemo.filecli.fileclicommand stat readme.md java com.example.picoclidemo.filecli.fileclicommand copy a.txt b.txt --force java com.example.picoclidemo.filecli.fileclicommand delete temp.log --dry-run
查看子命令帮助:
java com.example.picoclidemo.filecli.fileclicommand copy --help
输出类似:
usage: filecli copy [-f] <source> <target>
复制文件
<source> 源文件
<target> 目标文件
-f, --force 覆盖已存在文件
复用公共选项:mixin
很多子命令都会有公共参数,比如:
--verbose --profile --config
可以用 @mixin 复用。
公共选项:
package com.example.picoclidemo.filecli;
import picocli.commandline.option;
import java.nio.file.path;
public class commonoptions {
@option(names = {"-v", "--verbose"}, description = "输出详细日志")
boolean verbose;
@option(names = "--config", description = "配置文件路径")
path config;
}子命令使用:
@mixin private commonoptions commonoptions;
完整示例:
@command(name = "stat", description = "查看文件基本信息")
public class statcommand implements callable<integer> {
@mixin
private commonoptions commonoptions;
@parameters(index = "0", description = "文件路径")
private path file;
@override
public integer call() throws exception {
if (commonoptions.verbose) {
system.out.println("config=" + commonoptions.config);
}
system.out.println("path=" + file.toabsolutepath());
return 0;
}
}运行:
filecli stat readme.md --verbose --config ./app.properties
交互式输入
密码、token 这类敏感参数不适合直接写在命令里。
可以使用交互式输入:
@option(names = "--username", required = true, description = "用户名")
private string username;
@option(
names = "--password",
interactive = true,
arity = "0..1",
description = "密码,不在命令行中明文显示"
)
private char[] password;运行:
tool --username admin --password
控制台会提示输入密码。
相比:
tool --password 123456
交互式输入更适合敏感信息。
原因是命令行参数可能被历史记录、进程列表、审计日志记录下来。
参数校验
picocli 本身会做一些基本校验:
- 必填选项
- 参数个数
- 类型转换
- 枚举值是否合法
比如:
@option(names = "--port", required = true) private int port;
传入:
tool --port abc
会提示无法转换成整数。
更复杂的业务校验可以放到 call() 里。
示例:
@option(names = "--port", defaultvalue = "8080", description = "端口")
private int port;
@override
public integer call() {
if (port < 1 || port > 65535) {
system.err.println("端口范围必须是 1 到 65535");
return 2;
}
return 0;
}也可以使用自定义转换器提前拦截非法值。
错误处理和退出码
commandline.execute(args) 会返回退出码。
常见做法:
public static void main(string[] args) {
int exitcode = new commandline(new appcommand()).execute(args);
system.exit(exitcode);
}
常见退出码可以这样约定:
| 退出码 | 含义 |
|---|---|
0 | 成功 |
1 | 普通执行失败 |
2 | 参数或输入文件问题 |
3 | 目标已存在、状态冲突 |
picocli 内置常量:
commandline.exitcode.ok commandline.exitcode.usage commandline.exitcode.software
示例:
return commandline.exitcode.ok;
如果需要统一处理异常,可以配置异常处理器:
commandline commandline = new commandline(new fileclicommand());
commandline.setexecutionexceptionhandler((ex, cmd, parseresult) -> {
cmd.geterr().println("执行失败: " + ex.getmessage());
return 1;
});
int exitcode = commandline.execute(args);
system.exit(exitcode);帮助文档写好一点
命令行工具的帮助信息很重要。
推荐给 @command 写清楚这些字段:
@command(
name = "filecli",
description = "文件处理命令行工具",
synopsisheading = "%n用法:%n ",
descriptionheading = "%n说明:%n",
optionlistheading = "%n选项:%n",
parameterlistheading = "%n参数:%n",
commandlistheading = "%n子命令:%n",
mixinstandardhelpoptions = true,
version = "filecli 1.0.0"
)
选项描述里可以使用变量:
@option(
names = "--format",
defaultvalue = "text",
description = "输出格式: ${completion-candidates},默认 ${default-value}"
)
private outputformat format;
常见变量:
| 变量 | 说明 |
|---|---|
${default-value} | 默认值 |
${completion-candidates} | 候选值 |
${fallback-value} | 可选值参数的 fallback 值 |
命令补全
picocli 可以生成命令补全脚本。
补全能力包括:
- 命令名
- 子命令
- 选项
- 枚举候选值
添加 codegen 依赖后,可以生成脚本。
常见方式是在主命令里加入 autocomplete.generatecompletion 子命令:
@command(
name = "filecli",
mixinstandardhelpoptions = true,
subcommands = {
statcommand.class,
copycommand.class,
deletecommand.class,
picocli.autocomplete.generatecompletion.class
}
)
public class fileclicommand implements runnable {
@override
public void run() {
commandline.usage(this, system.out);
}
}生成脚本:
java -cp "target/filecli.jar" com.example.picoclidemo.filecli.fileclicommand generate-completion
也可以用 picocli.autocomplete 工具类按命令类生成。
不同 shell 的安装方式不同,常见是把生成脚本放到 bash、zsh 或 fish 的补全目录里。
打包成可执行 jar
命令行工具通常希望这样运行:
java -jar filecli.jar stat readme.md
可以用 maven shade plugin 打包 fat jar。
<build>
<plugins>
<plugin>
<groupid>org.apache.maven.plugins</groupid>
<artifactid>maven-shade-plugin</artifactid>
<version>3.6.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.manifestresourcetransformer">
<mainclass>com.example.picoclidemo.filecli.fileclicommand</mainclass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>打包:
mvn clean package
运行:
java -jar target/filecli-1.0.0.jar stat readme.md
为了像普通命令一样运行,可以加一个脚本。
filecli:
#!/usr/bin/env sh java -jar /opt/filecli/filecli-1.0.0.jar "$@"
授权:
chmod +x filecli
运行:
./filecli stat readme.md
graalvm native image
cli 工具很适合打成 native image。
好处:
- 启动快
- 不需要目标机器安装 jvm
- 分发更像普通二进制命令
picocli 对 graalvm native image 支持比较好。
maven native build tools 示例:
<profiles>
<profile>
<id>native</id>
<build>
<plugins>
<plugin>
<groupid>org.graalvm.buildtools</groupid>
<artifactid>native-maven-plugin</artifactid>
<version>1.1.2</version>
<extensions>true</extensions>
<configuration>
<mainclass>com.example.picoclidemo.filecli.fileclicommand</mainclass>
<imagename>filecli</imagename>
</configuration>
<executions>
<execution>
<id>build-native</id>
<goals>
<goal>compile-no-fork</goal>
</goals>
<phase>package</phase>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>构建:
mvn -pnative package
运行:
./target/filecli stat readme.md
native image 构建需要安装 graalvm 和本地 c 编译工具链。
如果项目引入了反射、动态代理、资源文件,可能还需要额外配置。纯 picocli 小工具通常比较顺。
spring boot 集成
如果 cli 需要复用 spring bean,比如:
userserviceorderservicerepository- 配置文件
- 数据源
可以使用 picocli-spring-boot-starter。
依赖:
<dependency>
<groupid>info.picocli</groupid>
<artifactid>picocli-spring-boot-starter</artifactid>
<version>4.7.7</version>
</dependency>命令类:
package com.example.bootcli;
import org.springframework.stereotype.component;
import picocli.commandline.command;
import picocli.commandline.option;
import java.util.concurrent.callable;
@component
@command(name = "user", description = "用户管理命令", mixinstandardhelpoptions = true)
public class usercommand implements callable<integer> {
private final userservice userservice;
@option(names = "--list", description = "列出用户")
private boolean list;
public usercommand(userservice userservice) {
this.userservice = userservice;
}
@override
public integer call() {
if (list) {
userservice.findall().foreach(system.out::println);
return 0;
}
system.out.println("没有指定操作");
return 2;
}
}启动类:
package com.example.bootcli;
import org.springframework.boot.commandlinerunner;
import org.springframework.boot.springapplication;
import org.springframework.boot.autoconfigure.springbootapplication;
import picocli.commandline;
import picocli.commandline.ifactory;
@springbootapplication
public class bootcliapplication implements commandlinerunner {
private final usercommand usercommand;
private final ifactory factory;
public bootcliapplication(usercommand usercommand, ifactory factory) {
this.usercommand = usercommand;
this.factory = factory;
}
public static void main(string[] args) {
springapplication.run(bootcliapplication.class, args);
}
@override
public void run(string... args) {
int exitcode = new commandline(usercommand, factory).execute(args);
system.exit(exitcode);
}
}运行:
java -jar boot-cli.jar --list
这种方式的关键点是:
命令类交给 spring 管理 commandline 使用 spring 提供的 ifactory 创建对象 子命令、转换器等也可以走 spring 注入
如果只是一个很小的 cli,不一定需要 spring boot。spring boot 启动成本更高,适合需要复用完整业务容器的场景。
单元测试
cli 工具也应该测试。
picocli 测试很方便,可以直接构造命令对象,捕获输出。
示例命令:
@command(name = "hello")
class hellocommand implements callable<integer> {
@parameters(index = "0")
string name;
printwriter out = new printwriter(system.out, true);
@override
public integer call() {
out.println("hello, " + name);
return 0;
}
}测试:
package com.example.picoclidemo;
import org.junit.jupiter.api.test;
import picocli.commandline;
import java.io.printwriter;
import java.io.stringwriter;
import static org.junit.jupiter.api.assertions.assertequals;
import static org.junit.jupiter.api.assertions.asserttrue;
class hellocommandtest {
@test
void shouldprinthello() {
hellocommand command = new hellocommand();
stringwriter output = new stringwriter();
command.out = new printwriter(output);
int exitcode = new commandline(command).execute("张三");
assertequals(0, exitcode);
asserttrue(output.tostring().contains("hello, 张三"));
}
}也可以直接测试错误参数:
@test
void shouldreturnusageexitcodewhenmissingname() {
int exitcode = new commandline(new hellocommand()).execute();
assertequals(commandline.exitcode.usage, exitcode);
}和 apache commons cli 的区别
java 里还有一个老牌库叫 apache commons cli。
两者大概区别:
| 对比项 | picocli | apache commons cli |
|---|---|---|
| 开发方式 | 注解驱动,也支持编程式 api | 编程式 api |
| 帮助文档 | 自动生成能力强 | 相对基础 |
| 子命令 | 支持较好 | 需要自行组织 |
| 类型转换 | 内置很多类型 | 通常手动取字符串再转换 |
| graalvm | 支持较好 | 不是主要卖点 |
| 适合场景 | 完整 cli 工具 | 简单参数解析 |
新写命令行工具,picocli 更省事。
如果只是解析几个参数,commons cli 也够用。
常见坑
main 方法里没有 system.exit
错误写法:
new commandline(new appcommand()).execute(args);
脚本调用时无法正确拿到退出码。
推荐:
int exitcode = new commandline(new appcommand()).execute(args); system.exit(exitcode);
还在使用 commandline.run 或 call
旧示例里经常看到:
commandline.run(new appcommand(), args); commandline.call(new appcommand(), args);
这些便捷方法在 picocli 4.x 里已经不推荐。
推荐:
new commandline(new appcommand()).execute(args);
布尔参数写成 boolean 但没有默认值
@option(names = "--verbose") private boolean verbose;
如果没传,值是 null。
大部分布尔开关用基本类型更简单:
@option(names = "--verbose") private boolean verbose;
位置参数顺序不清楚
@parameters private path source; @parameters private path target;
多个位置参数建议明确 index:
@parameters(index = "0") private path source; @parameters(index = "1") private path target;
选项名太随意
不建议写一堆只有短选项的参数:
-a -b -c -x
正式工具最好短选项和长选项都提供:
@option(names = {"-f", "--file"})
@option(names = {"-o", "--output"})
@option(names = {"-v", "--verbose"})
长选项更适合脚本,可读性更好。
密码直接从参数传入
不推荐:
tool --password 123456
更推荐:
tool --password
配合:
@option(names = "--password", interactive = true, arity = "0..1") private char[] password;
总结
picocli 可以按这条线理解:
@command 定义命令 @option 定义具名选项 @parameters 定义位置参数 commandline.execute 负责解析和执行 callable<integer> 返回退出码 subcommands 组织复杂工具 mixin 复用公共参数
日常开发可以按这个顺序落地:
- 先写单命令 demo
- 再补齐
--help和--version - 参数尽量用明确类型,比如
path、int、enum - 复杂工具拆成子命令
- 公共选项用
@mixin - 正式发布时打包成可执行 jar
- 对启动速度敏感时考虑 native image
- 需要复用业务 bean 时接入 spring boot
picocli 的价值不是“把参数解析换成注解”这么简单。真正省事的地方在于帮助文档、错误提示、子命令、类型转换、退出码这些细节都已经有一套成熟约定。命令行工具越往后做,越能感受到这些约定带来的稳定性。
到此这篇关于java picocli 实战指南:用注解写出好用的命令行工具的文章就介绍到这了,更多相关java picocli命令行工具内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论