相信每个Java程序员都曾使用过 Scanner
,因编写出一个命令行程序而兴奋不已。
命令行程序也颇为实用,然而,使用Java来编写一个功能强大的命令行程序却并不容易,主要有以下几方面的痛点:
- 没有成熟的框架来封装参数接收、参数提示以及参数校验
- 很难处理参数的互斥以及特定命令的相互依赖关系
- 无法进行命令自动补全
- 由于JVM解释执行字节码,并且JIT无法在短时执行中发挥作用,Java命令行程序启动缓慢
- 集成SpringBoot及其它组件后,启动更加缓慢
上述这些问题都可以使用Picocli来解决
本文主要向大家介绍Picocli,以及分析它是如何解决上述的问题,并介绍使用其构建一个控制台程序的基本流程,详细的使用指南请至官方文档。
官网:https://picocli.info
基本介绍
Picocli 致力于以最简洁的方式来创建一个基于JVM的功能强大的命令行程序。Picocli aims to be the easiest way to create rich command line applications that can run on and off the JVM.
下图是利用Picocli构建命令行程序,输出 checksum -h
后打印出的帮助文档:
快速开始
定义一个命令有两种方式:
- 使用成员属性来接收命令行参数,实现
Callable
接口并覆写call()
方法定义业务流程 - 使用类方法的参数来接收命令行参数,方法内部就是业务流程,个人推荐这种。
以下是两种定义方法:
实现Callable接口
// 定义checksum命令,以及命令的提示信息
@Command(name = "checksum", mixinStandardHelpOptions = true, version = "checksum 4.0",
description = "Prints the checksum (MD5 by default) of a file to STDOUT.")
class CheckSum implements Callable<Integer> {
@Parameters(index = "0", description = "The file whose checksum to calculate.")
private File file;
@Option(names = {"-a", "--algorithm"}, description = "MD5, SHA-1, SHA-256, ...")
private String algorithm = "MD5";
@Override
public Integer call() throws Exception { // your business logic goes here...
byte[] fileContents = Files.readAllBytes(file.toPath());
byte[] digest = MessageDigest.getInstance(algorithm).digest(fileContents);
System.out.printf("%0" + (digest.length*2) + "x%n", new BigInteger(1, digest));
return 0;
}
// this example implements Callable, so parsing, error handling and handling user
// requests for usage help or version help can be done with one line of code.
public static void main(String... args) {
int exitCode = new CommandLine(new CheckSum()).execute(args);
System.exit(exitCode);
}
}
类方法
# 上述代码可简化为:
@Command(name = "checksum", mixinStandardHelpOptions = true, version = "checksum 4.0",
description = "Prints the checksum (MD5 by default) of a file to STDOUT.")
public int checkSum(@Parameters(index = "0", description = "The file whose checksum to calculate.") File file,
@Option(names = {"-a", "--algorithm"}, description = "MD5, SHA-1, SHA-256, ...") String algorithm) throws Exception {
byte[] fileContents = Files.readAllBytes(file.toPath());
byte[] digest = MessageDigest.getInstance(algorithm).digest(fileContents);
System.out.printf("%0" + (digest.length * 2) + "x%n", new BigInteger(1, digest));
return 0;
}
如何解决痛点
相信你已经对Picocli有了初步了解,下面就开始回答Picocli是如何解决文章开头提出的痛点的。
@Option与@Parameter
不同点
@Option
是选项参数,分为参数名和参数内容,使用参数名来区分不同参数@Parameter
为位置参数,使用位置来区分不同参数@Option
可以叠加@Option
修饰的参数若为布尔类型,则可以不加省略参数内容
相同点
- 都可以定义默认值
- 都可以为可选参数,默认
@Option(required = false)
,@Parameter
默认是必填,但可以通过arity
属性来控制参数个数,arity = "0..1"
表示参数为0 - 1个。 - 都可以使用
interactive = true
开启交互模式输入,例如密码等敏感信息
arity
的默认值以及使用方法:
详见:https://picocli.info/#_arity
丰富的参数类型
如果我们使用Scanner读取参数,我们需要手动进行参数解析和类型转换,而Picocli提供了开箱即用的参数类型解析器,支持绝大部分的Java常用类,可以提前对类型进行检查,并且简化了类型解析过程。
详见:https://picocli.info/#_built_...
参数校验
Picocli支持多种参数校验方式:
- 内置的required和arity检查
- 手动在业务中检查
- JSR-380 BeanValidation注解
BeanValidation注解在Spring MVC的参数校验中经常出现,同样Picocli也对其支持,如常见注解:@NotNull
, @NotEmpty
, @Min
, @Max
等,强大而实用。
详见:https://picocli.info/#_valida...
@ArgGroup(参数组)
@ArgGroup
用来定义一组参数,通过 @ArgGroup(exclusive = ture/false, multiplicity = "...")
与 @Option(required=ture/false)
和 @Parameter(@arity = "...")
结合,可以实现以下功能:
- 一组互斥命令
- 一组参数用户必须至少输入一个
- 一组具有依赖关系的命令,即若未输入某一参数,就不能输入某些参数。
- 复合参数,
@ArgGroup
可嵌套
详见:https://picocli.info/#_argument_groups
SpringBoot集成
Picocli可以轻松地与SpringBoot结合.
<dependency>
<groupId>info.picocli</groupId>
<artifactId>picocli-spring-boot-starter</artifactId>
<version>${picocli.version}</version>
</dependency>
@SpringBootApplication
public class MySpringMailer implements CommandLineRunner, ExitCodeGenerator {
private IFactory factory;
private MailCommand mailCommand;
private int exitCode;
// 构造器注入
MySpringMailer(IFactory factory, MailCommand mailCommand) {
this.factory = factory;
this.mailCommand = mailCommand;
}
@Override
public void run(String... args) {
// let picocli parse command line args and run the business logic
exitCode = new CommandLine(mailCommand, factory).execute(args);
}
@Override
public int getExitCode() {
return exitCode;
}
public static void main(String[] args) {
// let Spring instantiate and inject dependencies
System.exit(SpringApplication.exit(SpringApplication.run(MySpringMailer.class, args)));
}
}
详见:https://picocli.info/#_spring_boot_example
自动补全
Picocli可以自动生成命令补全脚本,从而实现按下<TAB>键的时候,可以进行补全提示和自动补全。
详见:https://picocli.info/#_tab_autocomplete
执行命令
使用Picocli构建的命令行程序的使用方式大致分为两种:
- 使用
alias
命令来简化执行java -jar
- 打包为可执行文件,加入环境变量
alias
alias mycommand='java -cp "/path/to/picocli-4.6.1.jar:/path/to/myapp.jar" org.myorg.MainClass'
GraalVM Native
GraalVM Native Image 使得开发者可以AOT(ahead-of-time compile 提前)编译Java代码为可执行文件,也就是一个native image。
众所周知,以往启动一个SpringBoot程序,一般都会花费几十秒,这对命令行程序的使用者非常不友好,因为每一次命令都需要很长的延时才能得到响应。
而经过GraalVM Native Image编译后的可执行文件启动速度非常快,快到足够满足命令行程序所需的响应时延。
同时,我们能够依托Spring Native项目提供的依赖和Maven插件,来简化打包流程。
详见:Picocli文档:https://picocli.info/#_graalvm_native_image
Spring Native文档:https://docs.spring.io/spring-native/docs/current/reference/htmlsingle/
示例项目:https://docs.spring.io/spring-native/docs/current/reference/htmlsingle/
结语
由于篇幅和时间有限,本文只分享了Picocli的特性与使用方式,建议读者查看官方文档以获得最新、最全的使用指南。
之后若有时间或者大家有兴趣,我会继续更新详细的使用方式。
感谢您阅读本文,您的关注与点赞是对我最大的支持!
关注我的公众号“语冰Yubing”可接收最新推送,里面也有我分享的一些优质资源。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。