https://picocli.info/#_introduction

前言

相信每个Java程序员都曾使用过Scanner ,因编写出一个命令行程序而兴奋不已。
命令行程序也颇为实用,然而,使用Java来编写一个功能强大的命令行程序却并不容易,主要有以下几方面的痛点:

  • 没有成熟的框架来封装参数接收、参数提示以及参数校验
  • 很难处理参数的互斥以及特定命令的相互依赖关系
  • 无法进行命令自动补全
  • 由于JVM解释执行字节码,并且JIT无法在短时执行中发挥作用,Java命令行程序启动缓慢
  • 集成SpringBoot及其它组件后,启动更加缓慢

上述这些问题都可以使用Picocli来解决

引用:https://blog.csdn.net/qq_40419564/article/details/115290878

Picocli 基本介绍

Every main method deserves picocli!
Picocli aims to be the easiest way to create rich command line applications that can run on and off the JVM.

入门

引入maven依赖

<dependency><groupId>info.picocli</groupId><artifactId>picocli</artifactId><version>4.6.3</version></dependency>

应用demo

@CommandLine.Command(name = "checksum", mixinStandardHelpOptions = true, version = "checksum 4.0",description = "Prints the checksum (SHA-256 by default) of a file to STDOUT.")
public class CheckSum implements Callable<Integer> {@CommandLine.Parameters(index = "0", description = "The file whose checksum to calculate.")private File file;@CommandLine.Option(names = {"-a", "--algorithm"}, description = "MD5, SHA-1, SHA-256, ...")private String algorithm = "SHA-256";@Overridepublic 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;}public static void main(String... args) {int exitCode = new CommandLine(new CheckSum()).execute(args);System.exit(exitCode);}
}

maven打包

<build><finalName>demo1</finalName><plugins><plugin><artifactId>maven-assembly-plugin</artifactId><configuration><descriptorRefs><descriptorRef>jar-with-dependencies</descriptorRef></descriptorRefs><archive><manifest><mainClass>cn.jhs.CheckSum</mainClass></manifest></archive></configuration><executions><execution><id>make-assembly</id><phase>package</phase><goals><goal>single</goal></goals></execution></executions></plugin></plugins></build>

执行命令mvn clean package

Command

> java -jar target/demo1-jar-with-dependencies.jar
Missing required parameter: '<file>'
Usage: checksum [-hV] [-a=<algorithm>] <file>
Prints the checksum (SHA-256 by default) of a file to STDOUT.<file>      The file whose checksum to calculate.-a, --algorithm=<algorithm>MD5, SHA-1, SHA-256, ...-h, --help      Show this help message and exit.-V, --version   Print version information and exit.> echo "hello" > hello.txt
> java -jar target/demo1-jar-with-dependencies.jar hello.txt
5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03

使用别名

> alias checksum=java\ -jar\ target/demo1-jar-with-dependencies.jar> checksum hello.txt -a SHA-1
f572d396fae9206628714fb2ce00f72e94f2258f


Options and Parameters

Command line arguments可以分为optionspositional parameters
- option有一个名称

  • positional parameters位置参数通常是 option后面的值,但它们可能是混合的。

Options

option必须有一个或多个名称。 Picocli 允许您使用任何您想要的选项名称。

默认情况下,option名称区分大小写.

class Tar {@Option(names = "-c", description = "create a new archive")boolean create;@Option(names = { "-f", "--file" }, paramLabel = "ARCHIVE", description = "the archive file")File archive;@Parameters(paramLabel = "FILE", description = "one or more files to archive")File[] files;@Option(names = { "-h", "--help" }, usageHelp = true, description = "display a help message")private boolean helpRequested = false;
}

TestCase

String[] args = { "-c", "--file", "result.tar", "file1.txt", "file2.txt" };
Tar tar = new Tar();
new CommandLine(tar).parseArgs(args);assert !tar.helpRequested;
assert  tar.create;
assert  tar.archive.equals(new File("result.tar"));
assert  Arrays.equals(tar.files, new File[] {new File("file1.txt"), new File("file2.txt")});

Interactive (Password) Options

对于标记为Interactive Optionspositional parameters,会提示用户在控制台上输入一个值。

交互式

class Login implements Callable<Integer> {@Option(names = {"-u", "--user"}, description = "User name")String user;//响应式 Option@Option(names = {"-p", "--password"}, description = "Passphrase", interactive = true)char[] password;public Integer call() throws Exception {byte[] bytes = new byte[password.length];for (int i = 0; i < bytes.length; i++) { bytes[i] = (byte) password[i]; }MessageDigest md = MessageDigest.getInstance("SHA-256");md.update(bytes);System.out.printf("Hi %s, your password is hashed to %s.%n", user, base64(md.digest()));// null out the arrays when doneArrays.fill(bytes, (byte) 0);Arrays.fill(password, ' ');return 0;}private String base64(byte[] arr) { /* ... */ }
}

testcase
执行命令:new CommandLine(new Login()).execute("-u", "user123", "-p");
然后会提示用户输入一个值:

Enter value for --password (Passphrase):

在 Java 6 或更高版本上运行时,用户输入不会回显到控制台。
用户输入密码值并按下回车后,将call()调用该方法,该方法将打印如下内容:

Hi user123, your passphrase is hashed to 75K3eLr+dx6JJFuJ7LwIpEpOFmwGZZkRiB84PURz6U8=.#### 可选的 Interactive
默认情况下,` Interactive  Options`会导致应用程序等待标准输入上的输入。
对于即需要以交互方式 又需要以批处理模式运行的命令,如果该选项**可以选择的**,这将很有用。**arity**
```java
@Option(names = "--user")
String user;@Option(names = "--password", arity = "0..1", interactive = true)
char[] password;
  • 通过以下输入,密码字段将被初始化为“123”,而不提示用户输入:--password 123 --user Joe
  • 但是,如果未指定密码,--password --user Joe , 则会提示用户输入密码。

Short (POSIX) Options

class ClusteredShortOptions {@Option(names = "-a") boolean aaa;@Option(names = "-b") boolean bbb;@Option(names = "-c") boolean ccc;@Option(names = "-f") String  file;
}

以下命令行参数都是等效的,解析它们会得到相同的结果:

<command> -abcfInputFile.txt
<command> -abcf=InputFile.txt
<command> -abc -f=InputFile.txt
<command> -ab -cf=InputFile.txt
<command> -a -b -c -fInputFile.txt
<command> -a -b -c -f InputFile.txt
<command> -a -b -c -f=InputFile.txt
...

Boolean Options

Boolean Options通常不需要参数:在命令行中指定选项名称就足够了。

class BooleanOptions {@Option(names = "-x") boolean x;
}
  • x 的值默认为 false,
  • 如果在命令行上指定了-x,则设置为 true(与默认值相反)。
  • 如果在命令行上多次指定-x,则 x 的值保持为true

Negatable Options-否定选项

@Command(name = "negatable-options-demo")
class NegatableOptionsDemo {@Option(names = "--verbose",           negatable = true) boolean verbose;@Option(names = "-XX:+PrintGCDetails", negatable = true) boolean printGCDetails;@Option(names = "-XX:-UseG1GC",        negatable = true) boolean useG1GC = true;
}

上述示例的使用帮助如下所示:

Usage: negatable-options-demo [--[no-]verbose] [-XX:(+|-)PrintGCDetails][-XX:(+|-)UseG1GC]--[no-]verbose     Show verbose output-XX:(+|-)PrintGCDetails Prints GC details-XX:(+|-)UseG1GC   Use G1 algorithm for GC


Positional Parameters

Explicit Index - 显式索引

使用[0,+oo)索引属性来准确指定要捕获的参数。数组或集合字段可以捕获多个值。

class PositionalParameters {@Parameters(index = "0")    InetAddress host;@Parameters(index = "1")    int port;@Parameters(index = "2..*") File[] files;@Parameters(hidden = true)  // "hidden": don't show this parameter in usage help messageList<String> allParameters; // no "index" attribute: captures _all_ arguments
}

testcase

String[] args = { "localhost", "12345", "file1.txt", "file2.txt" };
PositionalParameters params = CommandLine.populateCommand(new PositionalParameters(), args);assert params.host.getHostName().equals("localhost");
assert params.port == 12345;
assert Arrays.equals(params.files, new File[] {new File("file1.txt"), new File("file2.txt")});assert params.allParameters.equals(Arrays.asList(args));

Omitting the Index -省略索引

可以省略 index 属性。

  • 对于多值位置参数(数组或集合),省略 index 属性意味着该字段捕获所有位置参数(相当于 index = "0..*")。
  • 对于单值位置参数
    • 在 picocli 4.3 之前,单值位置参数的默认索引也是 index = "0..*",即使只有一个值(通常是第一个参数) 可以被捕获。
    • 从 4.3 版开始,picocli 根据同一命令中定义的其他位置参数自动分配索引。**Automatic Parameter Indexes **

Mixing Options and Positional Parameters

 class Mixed {@ParametersList<String> positional;@Option(names = "-o")List<String> options;
}

testcase

 String[] args = { "param0", "-o", "AAA", "param1", "param2", "-o", "BBB", "param3" };
Mixed mixed = new Mixed();
new CommandLine(mixed).parseArgs(args);assert mixed.positional.equals(Arrays.asList("param0", "param1", "param2", "param3");
assert mixed.options.equals   (Arrays.asList("AAA", "BBB"));

Double dash (–)

当命令行参数之一只是两个破折号而没有附加任何字符 (--) 时,picocli 将所有后续参数解释为Positional Parameters,甚至是与选项名称匹配的参数

class DoubleDashDemo {@Option(names = "-v")     boolean verbose;@Option(names = "-files") List<String> files;@Parameters               List<String> params;
}

testcase

String[] args = { "-v", "--", "-files", "file1", "file2" };
DoubleDashDemo demo = new DoubleDashDemo();
new CommandLine(demo).parseArgs(args);assert demo.verbose;
assert demo.files == null;
assert demo.params.equals(Arrays.asList("-files", "file1", "file2"));

@-files

长命令行的参数文件

假设有文件:/home/foo/args,内容如下

# This line is a comment and is ignored.
ABC -option=123
'X Y Z'

执行命令: java MyCommand @/home/foo/args
等价于执行:java MyCommand ABC -option=123 "X Y Z"

encoding
若要执行命令: java -DFile.encoding=UTF8 MyCommand ABC -option=123 "X Y Z"
可以通过:

SET JAVA_TOOL_OPTIONS=-Dfile.encoding=UTF8
java MyCommand ABC -option=123 "X Y Z"

@-files Usage Help: showAtFileInUsageHelp

@Command(name = "myapp", showAtFileInUsageHelp = true,mixinStandardHelpOptions = true, description = "Example command.")
class MyApp {@Parameters(description = "A file.") File file;
}

执行命令结果如下:

Usage: myapp [-hV] [@<filename>...] <file>
Example command.[@<filename>...]   One or more argument files containing options.<file>             A file.-h, --help             Show this help message and exit.-V, --version          Print version information and exit.

增加了-h -V选项。



Subcommands

Subcommands复杂的命令行工具,例如著名的 git 工具,有许多子命令(例如,commit、push 等),每个子命令都有自己的一组选项和位置参数。 Picocli 使得使用subcommandssub-subcommands的命令变得非常容易,达到任何深度。

例1

@Command(name = "foo", subcommands = Bar.class)
class Foo implements Callable<Integer> {@Option(names = "-x") int x;@Override public Integer call() {System.out.printf("hi from foo, x=%d%n", x);boolean ok = true;return ok ? 0 : 1; // exit code}public static void main(String... args) {int exitCode = new CommandLine(new Foo()).execute(args);System.exit(exitCode);}
}@Command(name = "bar", description = "I'm a subcommand of `foo`")
class Bar implements Callable<Integer> {@Option(names = "-y") int y;@Override public Integer call() {System.out.printf("hi from bar, y=%d%n", y);return 23;}@Command(name = "baz", description = "I'm a subcommand of `bar`")int baz(@Option(names = "-z") int z) {System.out.printf("hi from baz, z=%d%n", z);return 45;}
}

testcase`

alias foo='java Foo'$ foo -x 123
hi from foo, x=123#check the exit code
$ echo $?
0###########
$ foo -x 123 bar -y=456
hi from bar, y=456#check the exit code
$ echo $?
23###########
foo bar baz -z=789
hi from baz, z=789#check the exit code
$ echo $?
45

声明式注册subcommand

@Command(subcommands = {GitStatus.class,GitCommit.class,GitAdd.class,GitBranch.class,GitCheckout.class,GitClone.class,GitDiff.class,GitMerge.class,GitPush.class,GitRebase.class,GitTag.class
})
public class Git { /* ... */ }

编程式注册subcommand

CommandLine commandLine = new CommandLine(new Git()).addSubcommand("status",   new GitStatus()).addSubcommand("commit",   new GitCommit()).addSubcommand("add",      new GitAdd()).addSubcommand("branch",   new GitBranch()).addSubcommand("checkout", new GitCheckout()).addSubcommand("clone",    new GitClone()).addSubcommand("diff",     new GitDiff()).addSubcommand("merge",    new GitMerge()).addSubcommand("push",     new GitPush()).addSubcommand("rebase",   new GitRebase()).addSubcommand("tag",      new GitTag());


SpringBoot集成

从 4.0 版开始,picocli 通过在picocli-spring-boot-starter模块中提供自定义工厂来支持 Spring Boot。

下面的 Spring Boot 示例应用程序为邮件客户端提供了一个命令行界面,可用于使用 SMTP 服务器发送电子邮件。收件人地址和主题行可以作为选项给出,而消息正文可以指定为参数文本。

MAVEN依赖

<dependency><groupId>info.picocli</groupId><artifactId>picocli-spring-boot-starter</artifactId><version>4.6.3</version>
</dependency>

import org.springframework.boot.*;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import picocli.CommandLine;
import picocli.CommandLine.IFactory;@SpringBootApplication
public class MySpringMailer implements CommandLineRunner, ExitCodeGenerator {//1. 自动注入 PicocliSpringFactoryprivate IFactory factory;    //2. 将有后面的@CommandLine.Command 注入    private MailCommand mailCommand; private int exitCode;// constructor injectionMySpringMailer(IFactory factory, MailCommand mailCommand) {this.factory = factory;this.mailCommand = mailCommand;}@Overridepublic void run(String... args) {// let picocli parse command line args and run the business logicexitCode = new CommandLine(mailCommand, factory).execute(args);}@Overridepublic int getExitCode() {return exitCode;}public static void main(String[] args) {// SpringApplication.exit() 关闭容器。//System.exit()关闭虚拟机。//作用: 在执行完spring任务后`mailCommand.run()`,关闭spring容器,关闭虚拟机。System.exit(SpringApplication.exit(SpringApplication.run(MySpringMailer.class, args)));}
}

Command

import org.springframework.stereotype.Component;
import org.springframework.beans.factory.annotation.Autowired;
import picocli.CommandLine.*;
import java.util.List;
import java.util.concurrent.Callable;@Component// 1.注册到spring容器
@Command(name = "mailCommand")
public class MailCommand implements Callable<Integer> {@Autowired private IMailService mailService; //3。由spring注入@Option(names = "--to", description = "email(s) of recipient(s)", required = true)List<String> to;@Option(names = "--subject", description = "Subject")String subject;@Parameters(description = "Message to be sent")String[] body = {};public Integer call() throws Exception {mailService.sendMessage(to, subject, String.join(" ", body));  //2.业务逻辑return 0;}
}

testcase
执行命令:

mvnw spring-boot:run
# ....   ____          _            __ _ _/\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \\\/  ___)| |_)| | | | | || (_| |  ) ) ) )'  |____| .__|_| |_|_| |_\__, | / / / /=========|_|==============|___/=/_/_/_/:: Spring Boot ::        (v2.3.5.RELEASE)
# ...
#2020-11-01 19:35:31.084  INFO 15724 --- [           main] info.picocli.demo.MySpringMailer         : Started MySpringMailer in 0.821 seconds (JVM running for 1.131)
Missing required option: '--to=<to>'
Usage: mailCommand [--subject=<subject>] --to=<to> [--to=<to>]... [<body>...][<body>...]           Message to be sent--subject=<subject>   Subject--to=<to>             email(s) of recipient(s)

补充:

SpringBoot.exit()

springboot-application-exit

SpringApplication提供了一个exit静态方法,用于关闭Spring容器,该方法还有一个参数exitCodeGenerators表示ExitCodeGenerator接口的数组。ExitCodeGenerator接口是一个生成退出码exitCode的生成器。

public static int exit(ApplicationContext context,ExitCodeGenerator... exitCodeGenerators) {Assert.notNull(context, "Context must not be null");int exitCode = 0; // 默认的退出码是0try {try {// 构造ExitCodeGenerator集合ExitCodeGenerators generators = new ExitCodeGenerators();// 获得Spring容器中所有的ExitCodeGenerator类型的beanCollection<ExitCodeGenerator> beans = context.getBeansOfType(ExitCodeGenerator.class).values();// 集合加上参数中的ExitCodeGenerator数组generators.addAll(exitCodeGenerators);// 集合加上Spring容器中的ExitCodeGenerator集合generators.addAll(beans);// 遍历每个ExitCodeGenerator,得到最终的退出码exitCode// 这里每个ExitCodeGenerator生成的退出码如果比0大,那么取最大的// 如果比0小,那么取最小的exitCode = generators.getExitCode();if (exitCode != 0) { // 如果退出码exitCode不为0,发布ExitCodeEvent事件context.publishEvent(new ExitCodeEvent(context, exitCode));}}finally {// 关闭Spring容器close(context);}}catch (Exception ex) {ex.printStackTrace();exitCode = (exitCode == 0 ? 1 : exitCode);}return exitCode;
}

假设有一个controller方法:

@Autowired
private ApplicationContext applicationContext;@PostMapping("/stop")
public String stop() {// 加上自己的权限验证SpringApplication.exit(applicationContext);return "ok";
}

System.exit(X)

System.exit(status):无论 status 为何值都会退出程序

  • System.exit(0); 整个程序正常退出,会执行注册的shutdown-hooks.
  • System.exit(非0); 整个程序异常退出

picocli-入门相关推荐

  1. 用Construct 2制作入门小游戏~

    今天在软导课上了解到了Construct 2这个神器,本零基础菜鸟决定尝试做一个简单的小游戏(实际上是入门的教程啊= = 首先呢,肯定是到官网下载软件啊,点击我下载~ 等安装完毕后我便按照新手教程开始 ...

  2. Docker入门六部曲——Swarm

    原文链接:http://www.dubby.cn/detail.html?id=8738 准备工作 安装Docker(版本最低1.13). 安装好Docker Compose,上一篇文章介绍过的. 安 ...

  3. Docker入门六部曲——Stack

    原文链接:http://www.dubby.cn/detail.html?id=8739 准备知识 安装Docker(版本最低1.13). 阅读完Docker入门六部曲--Swarm,并且完成其中介绍 ...

  4. Docker入门六部曲——服务

    原文链接:http://www.dubby.cn/detail.html?id=8735 准备 已经安装好Docker 1.13或者以上的版本. 安装好Docker Compose.如果你是用的是Do ...

  5. 【springboot】入门

    简介: springBoot是spring团队为了整合spring全家桶中的系列框架做研究出来的一个轻量级框架.随着spring4.0推出而推出,springBoot可以説是J2SEE的一站式解决方案 ...

  6. SpringBoot (一) :入门篇 Hello World

    什么是SpringBoot Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程.该框架使用了特定的方式来进行配置,从而使开发人员不 ...

  7. 入门指南目录页 -PaddlePaddle 飞桨 入门指南 FAQ合集-深度学习问题

    入门指南目录页 -PaddlePaddle 飞桨 入门指南 FAQ合集 GT_Zhang关注 0.1012019.08.01 18:43:34字数 1,874阅读 795 Hi,欢迎各位来自Paddl ...

  8. 5 分钟入门 Google 最强NLP模型:BERT

    BERT (Bidirectional Encoder Representations from Transformers) 10月11日,Google AI Language 发布了论文 BERT: ...

  9. 命名实体识别入门教程(必看)

    关于开发自己的命名实体识别先期思路: 虽然网上有很多相关代码,但实际如何入门材料较少,故整理下: CRF:先期可以用人民日报语料库去做,步骤如下: https://blog.csdn.net/hude ...

  10. Shiro第一个程序:官方快速入门程序Qucickstart详解教程

    目录 一.下载解压 二.第一个Shiro程序 1. 导入依赖 2. 配置shiro配置文件 3. Quickstart.java 4. 启动测试 三.shiro.ini分析 四.Quickstart. ...

最新文章

  1. SpringBoot集成Redis用法笔记
  2. 编译 Linux2.6 内核总结【ZT】
  3. bzoj 2502: 清理雪道(有下界的最小流)
  4. python之定义默认参数_035-Python之定义默认参数
  5. 标签和标签选择器、label selector
  6. SQL第四周学习笔记---python操作数据库、游标、sqlite、mysql数据库银行转账
  7. ITIL4讲解: 组合管理
  8. Matlab实现小世界网络生成及其分析
  9. symbian塞班系统支持格式
  10. 2003系统服务器设置,服务器2003系统设置
  11. redmibook螃蟹网卡信号差问题
  12. MAC获取公钥的步骤
  13. 如何网络推广快,怎么快速推广网站
  14. 如何安装鸿蒙应用,华为鸿蒙OS系统手机怎么安装第三方的应用程序?
  15. 怎样在LaTeX中使用中文
  16. VS 2017 解决 C2001错误 常量中有换行符
  17. 计算机考试感受作文,考试后的感受作文(通用10篇)
  18. Centos 基本命令
  19. 六则糟糕代码的优化方案
  20. [课业] 09 | 软工 | 软件工程的发展

热门文章

  1. vmware Workstation设置bios启动
  2. Spring 响应式编程,真香!!!
  3. mysql中返回上级_MySql 获取当前节点及递归所有上级节点
  4. MS10-046_LNK文件快捷方式漏洞的原理分析(多图杀猫)
  5. 我的梦想,我的飞翔! 南航天合2013校园招聘火热启动
  6. XPS学习笔记(一)
  7. 说说我理解的互联网共享精神
  8. C#实现倒计时的功能
  9. 太酷炫了,我用python画出了北上广深的地铁路线动态图
  10. php教室预约系统怎么做,教室预约管理系统