https://www.codenong.com/b-java-picocli-create-command-line-program/
1.简介
在本教程中,我们将使用picocli库,该库使我们可以轻松地用Java创建命令行程序。
首先,我们将创建一个Hello World命令。 然后,我们将通过部分复制gitcommand来深入了解该库的关键功能。
2.你好世界命令
让我们从简单的事情开始:Hello World命令!
首先,我们需要将依赖项添加到thepicocli项目中:
1 | <dependency> |
如我们所见,我们将使用库的3.9.6版本,尽管a4.0.0版本正在构建中(当前在alpha测试中可用)。
现在已经建立了依赖关系,让我们创建我们的Hello World命令。 为此,我们将使用库中的@Command注释:
1 | @Command( |
如我们所见,注释可以带有参数。 我们在这里只使用其中两个。 它们的目的是提供有关自动帮助消息的当前命令和文本的信息。
目前,使用此命令我们无能为力。 为了使其能够执行某些操作,我们需要添加一个调用便捷CommandLine.run(Runnable,String [])方法的主方法。 这需要两个参数:一个命令实例,因此必须实现Runnable接口;一个String数组,表示命令参数(选项,参数和子命令):
1 | public class HelloWorldCommand implements Runnable { |
现在,当我们运行main方法时,我们将看到控制台输出" Hello World!"。
打包到jar中后,我们可以使用java命令运行Hello World命令:
1 | java -cp"pathToPicocliJar;pathToCommandJar" com.baeldung.picoli.helloworld.HelloWorldCommand |
毫不奇怪,它还会输出" Hello World!" 字符串到控制台。
3.具体的用例
现在,我们已经了解了基础知识,我们将深入研究picocli库。 为了做到这一点,我们将部分复制一个流行的命令:git。
当然,其目的不是实现git命令的行为,而是重现git命令的可能性-存在哪些子命令以及哪些选项可用于特殊的子命令。
首先,我们必须像对Hello World命令那样创建一个GitCommand类:
1 | @Command |
4.添加子命令
gitcommand提供了许多子命令-add,commit,remote等。 在这里,我们将重点介绍添加和提交。
因此,我们的目标是在主命令中声明这两个子命令.Picocli提供了三种实现方法。
4.1。 在类上使用@Command注释
@Command批注提供了通过subcommands参数注册子命令的可能性:
1 | @Command( |
在本例中,我们添加了两个新类:GitAddCommand和GitCommitCommand。 两者都用@Command和ImplementRunnable进行注释。 给他们起一个名字很重要,因为picocli将使用这些名字来识别要执行的子命令:
1 | @Command( |
1 | @Command( |
因此,如果我们使用add作为参数运行main命令,则控制台将输出"将某些文件添加到暂存区"。
4.2。 在方法上使用@Command注释
声明子命令的另一种方法是创建@Command注释的方法,该方法表示GitCommand类中的那些命令:
1 | @Command(name ="add") |
这样,我们可以将业务逻辑直接实现到方法中,而无需创建单独的类来处理它。
4.3。 以编程方式添加子命令
最后,picocli为我们提供了以编程方式注册子命令的可能性。 这有点棘手,因为我们必须创建一个包装命令的CommandLine对象,然后向其中添加子命令:
1 | CommandLine commandLine = new CommandLine(new GitCommand()); |
在那之后,我们仍然必须运行命令,但是不能再使用CommandLine.run()方法了。 现在,我们必须在新创建的CommandLine对象上调用theparseWithHandler()方法:
1 | commandLine.parseWithHandler(new RunLast(), args); |
我们应该注意RunLast类的使用,该类告诉picocli运行最特定的子命令。 picocli还提供了其他两个命令处理程序:RunFirst和RunAll。 前者运行最高命令,而后者运行所有命令。
使用便捷方法CommandLine.run()时,默认情况下使用RunLast处理程序。
5.使用@Option注释管理选项
5.1。 无参数期权
现在让我们看看如何在命令中添加一些选项。 确实,我们想告诉ouradd命令它应该添加所有修改过的文件。 为此,我们将在@GitAddCommand类中添加一个@Option注释字段:
1 | @Option(names = {"-A","--all"}) |
如我们所见,注释采用了一个名称参数,该参数给出了选项的不同名称。 因此,使用-A或–all调用add命令会将allFiles字段设置为true。 因此,如果我们运行带有该选项的命令,控制台将显示"将所有文件添加到暂存区"。
5.2。 带参数的选项
如我们所见,对于不带参数的选项,它们的存在或不存在总是被评估为布尔值。
但是,可以注册带有参数的选项。 我们只需声明我们的字段为其他类型即可完成此操作。 让我们向我们的commit命令添加一个消息选项:
1 | @Option(names = {"-m","--message"}) |
毫不奇怪,当给定message选项时,该命令将在控制台上显示提交消息。 在本文的后面,我们将介绍库处理的类型以及如何处理其他类型。
5.3。 具有多个参数的选项
但是现在,如果我们希望我们的命令接受多条消息,就像真正的git commit命令那样,该怎么办? 不用担心,让我们将字段设为数组或Collection,我们已经完成很多工作:
1 | @Option(names = {"-m","--message"}) |
现在,我们可以多次使用message选项:
1 | commit -m"My commit is great" -m"My commit is beautiful" |
但是,我们可能也只想给该选项一次,并用正则表达式定界符分隔不同的参数。 因此,我们可以使用@Option注释的split参数:
1 | @Option(names = {"-m","--message"}, split =",") |
现在,我们可以通过-m"我的承诺很棒","我的承诺很棒"来达到与上述相同的结果。
5.4。 必选选项
有时,我们可能有一个必需的选项。 必需的参数(默认为false)允许我们执行以下操作:
1 | @Option(names = {"-m","--message"}, required = true) |
现在,如果不指定message选项,就无法调用commit命令。 如果我们尝试这样做,picocli将输出错误:
1 | Missing required option '--message=<messages>' |
6.管理位置参数
6.1。 捕获位置参数
现在,让我们关注我们的add命令,因为它还不是很强大。 我们只能决定添加所有文件,但是如果我们想添加特定文件,该怎么办?
我们可以使用另一种方法来执行此操作,但此处更好的选择是使用位置参数。 实际上,位置参数旨在捕获占据特定位置的命令参数,这些参数既不是子命令也不是选项。
在我们的示例中,这将使我们能够执行以下操作:
1 | add file1 file2 |
为了捕获位置参数,我们将使用@Parameters批注:
1 | @Parameters |
现在,我们前面的命令将输出:
1 | Adding file1 to the staging area |
6.2。 捕获位置参数的子集
由于注释的index参数,可以更精确地捕获要捕获的位置参数。 索引从零开始。 因此,如果我们定义:
1 | @Parameters(index="2..*") |
从第三个到最后,这将捕获与选项或子命令不匹配的参数。
索引可以是一个范围或单个数字,代表一个位置。
7.关于类型转换的一句话
正如我们在本教程前面所看到的,picocli本身可以处理一些类型转换。 例如,它将多个值映射到数组或集合,但也可以将参数映射到特定类型,例如当我们将路径类用于add命令时。
实际上,picocli带有许多预处理的类型。 这意味着我们可以直接使用这些类型,而不必考虑自己转换它们。
但是,我们可能需要将命令参数映射到已处理类型以外的其他类型。 对我们来说幸运的是,这要归功于ITypeConverter接口和CommandLine#registerConverter方法,该方法将类型与转换器相关联。
假设我们想将config子命令添加到我们的git命令中,但是我们不希望用户更改不存在的配置元素。 因此,我们决定将这些元素映射到一个枚举:
1 | public enum ConfigElement { |
另外,在我们新创建的GitConfigCommand类中,让我们添加两个位置参数:
1 | @Parameters(index ="0") |
这样,我们确保用户将无法更改不存在的配置元素。
最后,我们必须注册我们的转换器。 美妙的是,如果使用Java 8或更高版本,我们甚至不必创建实现ITypeConverter接口的类。 我们可以将lambda或方法引用传递给registerConverter()方法:
1 | CommandLine commandLine = new CommandLine(new GitCommand()); |
这发生在GitCommandmain()方法中。 请注意,我们必须放开convenienceCommandLine.run()方法。
与未处理的配置元素一起使用时,该命令将显示帮助消息以及一条信息,告诉我们无法将参数转换为ConfigElement:
1 | Invalid value for positional parameter at index 0 (<element>): |
8.与Spring Boot集成
最后,让我们看看如何将所有这些内容进行弹奏!
确实,我们可能正在Spring Boot环境中工作,并希望从我们的命令行程序中受益。 为了做到这一点,我们必须创建一个实现了CommandLineRunner接口的SpringBootApplication:
1 | @SpringBootApplication |
另外,让我们用Spring @Component注释注释所有命令和子命令,并自动装配应用程序中的所有内容:
1 | private GitCommand gitCommand; |
注意,我们必须自动装配每个子命令。 不幸的是,这是因为到目前为止,picocli在以声明方式(带有批注)进行声明时仍无法从Spring上下文中检索子命令。 因此,我们必须以编程方式自己进行连接:
1 | @Override |
现在,我们的命令行程序对Spring组件来说就像一个魅力。 因此,我们可以创建一些服务类并在命令中使用它们,而letSpring负责依赖注入。
9.结论
在本文中,我们已经了解了picoclilibrary的一些关键功能。 我们已经学习了如何创建新命令并向其中添加一些子命令。 我们已经看到了许多处理选项和位置参数的方法。 另外,我们还学习了如何实现自己的类型转换器以使命令强类型化。 最后,我们已经了解了如何将Spring Boot引入命令。
当然,还有很多事情可以发现。 该库提供了完整的文档。
至于本文的完整代码,可以在我们的GitHub上找到。