1. Overview
1.概述
In this tutorial, we’ll learn how to use JCommander to parse command-line parameters. We’ll explore several of its features as we build a simple command-line application.
在本教程中,我们将学习如何使用JCommander来解析命令行参数。我们将在构建一个简单的命令行应用程序时探索其若干功能。
2. Why JCommander?
2.为什么是JCommander?
“Because life is too short to parse command line parameters” – Cédric Beust
“因为生命太短暂,无法解析命令行参数” – Cédric Beust
JCommander, created by Cédric Beust, is an annotation-based library for parsing command-line parameters. It can reduce the effort of building command-line applications and help us provide a good user experience for them.
JCommander,由Cédric Beust创建,是一个基于注释的库,用于解析命令行参数。它可以减少构建命令行应用程序的工作量,并帮助我们为其提供良好的用户体验。
With JCommander, we can offload tricky tasks such as parsing, validation, and type conversions, to allow us to focus on our application logic.
通过JCommander,我们可以卸载一些棘手的任务,如解析、验证和类型转换,以使我们能够专注于我们的应用逻辑。
3. Setting up JCommander
3.设置JCommander
3.1. Maven Configuration
3.1.Maven配置
Let’s begin by adding the jcommander dependency in our pom.xml:
让我们首先在我们的pom.xml中添加jcommander依赖性。
<dependency>
<groupId>com.beust</groupId>
<artifactId>jcommander</artifactId>
<version>1.78</version>
</dependency>
3.2. Hello World
3.2.你好,世界
Let’s create a simple HelloWorldApp that takes a single input called name and prints a greeting, “Hello <name>”.
让我们创建一个简单的HelloWorldApp,它接受一个名为name的输入,并打印出一个问候语,“Hello <name>”。
Since JCommander binds command-line arguments to fields in a Java class, we’ll first define a HelloWorldArgs class with a field name annotated with @Parameter:
由于JCommander将命令行参数与Java类中的字段绑定在一起,我们将首先定义一个HelloWorldArgs类,其字段name用@Parameter注释。
class HelloWorldArgs {
@Parameter(
names = "--name",
description = "User name",
required = true
)
private String name;
}
Now, let’s use the JCommander class to parse the command-line arguments and assign the fields in our HelloWorldArgs object:
现在,让我们使用JCommander类来解析命令行参数,并将字段分配给我们的HelloWorldArgs对象中。
HelloWorldArgs jArgs = new HelloWorldArgs();
JCommander helloCmd = JCommander.newBuilder()
.addObject(jArgs)
.build();
helloCmd.parse(args);
System.out.println("Hello " + jArgs.getName());
Finally, let’s invoke the main class with the same arguments from the console:
最后,让我们用同样的参数从控制台调用主类。
$ java HelloWorldApp --name JavaWorld
Hello JavaWorld
4. Building a Real Application in JCommander
4.在JCommander中建立一个真实的应用程序
Now that we’re up and running, let’s consider a more complex use case — a command-line API client that interacts with a billing application such as Stripe, particularly the Metered (or usage-based) Billing scenario. This third-party billing service manages our subscriptions and invoicing.
现在我们已经开始运行,让我们考虑一个更复杂的用例–一个命令行API客户端,它与诸如Stripe等计费应用程序进行交互,特别是计费(或基于使用量的)计费场景。该第三方计费服务负责管理我们的订阅和开票。
Let’s imagine that we’re running a SaaS business, in which our customers buy subscriptions to our services and are billed for the number of API calls to our services per month. We’ll perform two operations in our client:
让我们想象一下,我们正在经营一个SaaS业务,我们的客户购买我们的服务订阅,并按每月调用我们服务的API数量计费。我们将在我们的客户端执行两个操作。
- submit: Submit quantity and unit price of usage for a customer against a given subscription
- fetch: Fetch charges for a customer based on the consumption on some or all of their subscriptions in the current month — we can get these charges aggregated over all the subscriptions or itemized by each subscription
We’ll build the API client as we go through the library’s features.
我们将在浏览该库的功能时建立API客户端。
Let’s begin!
让我们开始吧!
5. Defining a Parameter
5.定义一个参数
Let’s begin by defining the parameters that our application can use.
让我们首先定义我们的应用程序可以使用的参数。
5.1. The @Parameter Annotation
5.1.@Parameter 注释
Annotating a field with @Parameter tells JCommander to bind a matching command-line argument to it. @Parameter has attributes to describe the main parameter, such as:
用@Parameter注释一个字段,告诉JCommander将一个匹配的命令行参数与之绑定。@Parameter具有描述主要参数的属性,例如:。
- names – one or more names of the option, for example “–name” or “-n”
- description – the meaning behind the option, to help the end user
- required – whether the option is mandatory, defaults to false
- arity – number of additional parameters that the option consumes
Let’s configure a parameter customerId in our metered-billing scenario:
让我们在我们的计费方案中配置一个参数customerId。
@Parameter(
names = { "--customer", "-C" },
description = "Id of the Customer who's using the services",
arity = 1,
required = true
)
String customerId;
Now, let’s execute our command with the new “–customer” parameter:
现在,让我们用新的”-customer “参数执行我们的命令。
$ java App --customer cust0000001A
Read CustomerId: cust0000001A.
Likewise, we can use the shorter “-C” parameter to achieve the same effect:
同样,我们可以使用更短的”-C “参数来达到同样的效果。
$ java App -C cust0000001A
Read CustomerId: cust0000001A.
5.2. Required Parameters
5.2.所需参数
Where a parameter is mandatory, the application exits throwing a ParameterException if the user does not specify it:
如果一个参数是强制性的,如果用户没有指定它,应用程序退出时会抛出一个ParameterException。
$ java App
Exception in thread "main" com.beust.jcommander.ParameterException:
The following option is required: [--customer | -C]
We should note that, in general, any error in parsing the parameters results in a ParameterException in JCommander.
我们应该注意到,一般来说,解析参数的任何错误都会导致JCommander中出现ParameterException。
6. Built-In Types
6.内置的类型
6.1. IStringConverter Interface
6.1.IStringConverter 接口
JCommander performs type conversion from the command-line String input into the Java types in our parameter classes. The IStringConverter interface handles the type conversion of a parameter from String to any arbitrary type. So, all of JCommander’s built-in converters implement this interface.
JCommander执行类型转换,从命令行String输入到我们参数类中的Java类型。IStringConverter接口处理参数的类型转换,从String到任何任意类型。因此,所有JCommander的内置转换器都实现了这个接口。
Out of the box, JCommander comes with support for common data types such as String, Integer, Boolean, BigDecimal, and Enum.
开箱后,JCommander支持常见的数据类型,如String, Integer, Boolean, BigDecimal, 和Enum。
6.2. Single-Arity Types
6.2.单重类型
Arity relates to the number of additional parameters an option consumes. JCommander’s built-in parameter types have a default arity of one, except for Boolean and List. Therefore, common types such as String, Integer, BigDecimal, Long, and Enum, are single-arity types.
Arity与一个选项所消耗的额外参数的数量有关。JCommander的内置参数类型默认为1,除了Boolean和List。String、Integer、BigDecimal、Long,和Enum,都是单极类型。
6.3. Boolean Type
6.3.布尔类型
Fields of type boolean or Boolean don’t need any additional parameter – these options have an arity of zero.
boolean或Boolean类型的字段不需要任何额外的参数–这些选项的arity为零。
Let’s look at an example. Perhaps we want to fetch the charges for a customer, itemized by subscription. We can add a boolean field itemized, which is false by default:
让我们看一个例子。也许我们想获取一个客户的费用,按订阅情况逐项列出。我们可以添加一个boolean字段itemized,它默认为false。
@Parameter(
names = { "--itemized" }
)
private boolean itemized;
Our application would return aggregated charges with itemized set to false. When we invoke the command line with the itemized parameter, we set the field to true:
我们的应用程序将在itemized设置为false的情况下返回汇总的费用。当我们用itemized参数调用命令行时,我们将该字段设置为true。
$ java App --itemized
Read flag itemized: true.
This works well unless we have a use case where we always want itemized charges, unless specified otherwise. We could change the parameter to be notItemized, but it might be clearer to be able to provide false as the value of itemized.
这很有效,除非我们有一个用例,我们总是想要逐项收费,,除非另有规定。我们可以将参数改为notItemized,,但是能够提供false作为itemized的值可能更清楚。
Let’s introduce this behavior by using a default value true for the field, and setting its arity as one:
让我们通过为字段使用默认值true,并将其arity设置为1来引入这种行为。
@Parameter(
names = { "--itemized" },
arity = 1
)
private boolean itemized = true;
Now, when we specify the option, the value will be set to false:
现在,当我们指定该选项时,该值将被设置为false。
$ java App --itemized false
Read flag itemized: false.
7. List Types
7.列表类型
JCommander provides a few ways of binding arguments to List fields.
JCommander提供了几种将参数绑定到List字段的方法。
7.1. Specifying the Parameter Multiple Times
7.1.多次指定参数
Let’s assume we want to fetch the charges of only a subset of a customer’s subscriptions:
让我们假设我们只想获取客户订阅的一个子集的费用。
@Parameter(
names = { "--subscription", "-S" }
)
private List<String> subscriptionIds;
The field is not mandatory, and the application would fetch the charges across all the subscriptions if the parameter is not supplied. However, we can specify multiple subscriptions by using the parameter name multiple times:
该字段不是强制性的,如果不提供该参数,应用程序将获取所有订阅的费用。然而,我们可以通过多次使用参数名称来指定多个订阅。
$ java App -S subscriptionA001 -S subscriptionA002 -S subscriptionA003
Read Subscriptions: [subscriptionA001, subscriptionA002, subscriptionA003].
7.2. Binding Lists Using the Splitter
7.2.使用分割器绑定列表
Instead of specifying the option multiple times, let’s try to bind the list by passing a comma-separated String:
不要多次指定选项,让我们尝试通过传递逗号分隔的String来绑定列表。
$ java App -S subscriptionA001,subscriptionA002,subscriptionA003
Read Subscriptions: [subscriptionA001, subscriptionA002, subscriptionA003].
This uses a single parameter value (arity = 1) to represent a list. JCommander will use the class CommaParameterSplitter to bind the comma-separated String to our List.
这使用一个单一的参数值(arity = 1)来表示一个列表。JCommander将使用CommaParameterSplitter类来将逗号分隔的String绑定到我们的List。
7.3. Binding Lists Using a Custom Splitter
7.3.使用自定义分割器绑定列表
We can override the default splitter by implementing the IParameterSplitter interface:
我们可以通过实现IParameterSplitter接口来覆盖默认的分割器。
class ColonParameterSplitter implements IParameterSplitter {
@Override
public List split(String value) {
return asList(value.split(":"));
}
}
And then mapping the implementation to the splitter attribute in @Parameter:
然后将该实现映射到@Parameter中的splitter属性。
@Parameter(
names = { "--subscription", "-S" },
splitter = ColonParameterSplitter.class
)
private List<String> subscriptionIds;
Let’s try it out:
让我们来试一试。
$ java App -S "subscriptionA001:subscriptionA002:subscriptionA003"
Read Subscriptions: [subscriptionA001, subscriptionA002, subscriptionA003].
7.4. Variable Arity Lists
7.4.变量衔接列表
Variable arity allows us to declare lists that can take indefinite parameters, up to the next option. We can set the attribute variableArity as true to specify this behavior.
变量arity允许我们声明 列表可以接受无限的参数,直到下一个选项。我们可以将属性variableArity设置为true来指定这种行为。
Let’s try this to parse subscriptions:
让我们试着这样来解析订阅。
@Parameter(
names = { "--subscription", "-S" },
variableArity = true
)
private List<String> subscriptionIds;
And when we run our command:
而当我们运行我们的命令时。
$ java App -S subscriptionA001 subscriptionA002 subscriptionA003 --itemized
Read Subscriptions: [subscriptionA001, subscriptionA002, subscriptionA003].
JCommander binds all input arguments following the option “-S” to the list field, until the next option or the end of the command.
JCommander将选项”-S “之后的所有输入参数绑定到列表字段,直到下一个选项或命令结束。
7.5. Fixed Arity Lists
7.5.固定构词法列表
So far we’ve seen unbounded lists, where we can pass as many list items as we wish. Sometimes, we may want to limit the number of items passed to a List field. To do this, we can specify an integer arity value for a List field to make it bounded:
到目前为止,我们已经看到了无界的列表,在那里我们可以传递任意多的列表项。有时,我们可能想限制传递给List字段的项的数量。为此,我们可以为List字段指定一个整数arity值以使其有界。
@Parameter(
names = { "--subscription", "-S" },
arity = 2
)
private List<String> subscriptionIds;
Fixed arity forces a check on the number of parameters passed to a List option and throws a ParameterException in case of a violation:
固定的arity强制检查传递给List选项的参数数量,如果违反,则抛出ParameterException。
$ java App -S subscriptionA001 subscriptionA002 subscriptionA003
Was passed main parameter 'subscriptionA003' but no main parameter was defined in your arg class
The error message suggests that since JCommander expected only two arguments, it tried to parse the extra input parameter “subscriptionA003” as the next option.
错误信息表明,由于JCommander预计只有两个参数,它试图解析额外的输入参数” subscriptionA003 “作为下一个选项。
8. Custom Types
8.自定义类型
We can also bind parameters by writing custom converters. Like built-in converters, custom converters must implement the IStringConverter interface.
我们还可以通过编写自定义转换器来绑定参数。与内置转换器一样,自定义转换器必须实现IStringConverter接口。
Let’s write a converter for parsing an ISO8601 timestamp:
让我们写一个转换器来解析ISO8601时间戳。
class ISO8601TimestampConverter implements IStringConverter<Instant> {
private static final DateTimeFormatter TS_FORMATTER =
DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss");
@Override
public Instant convert(String value) {
try {
return LocalDateTime
.parse(value, TS_FORMATTER)
.atOffset(ZoneOffset.UTC)
.toInstant();
} catch (DateTimeParseException e) {
throw new ParameterException("Invalid timestamp");
}
}
}
This code will parse the input String and return an Instant, throwing a ParameterException if there’s a conversion error. We can use this converter by binding it to a field of type Instant using the converter attribute in @Parameter:
这段代码将解析输入的String,并返回一个Instant,如果出现转换错误,则抛出ParameterException。我们可以通过使用@Parameter中的converter属性将其绑定到Instant类型的字段来使用这个转换器。
@Parameter(
names = { "--timestamp" },
converter = ISO8601TimestampConverter.class
)
private Instant timestamp;
Let’s see it in action:
让我们看看它的行动。
$ java App --timestamp 2019-10-03T10:58:00
Read timestamp: 2019-10-03T10:58:00Z.
9. Validating Parameters
9.验证参数
JCommander provides a few default validations:
JCommander提供了一些默认的验证方法。
- whether required parameters are supplied
- if the number of parameters specified matches the arity of a field
- whether each String parameter can be converted into the corresponding field’s type
In addition, we may wish to add custom validations. For instance, let’s assume that the customer IDs must be UUIDs.
此外,我们可能希望添加自定义验证。例如,我们假设客户的ID必须是UUIDs。
We can write a validator for the customer field that implements the interface IParameterValidator:
我们可以为客户字段写一个验证器,实现IParameterValidator接口。
class UUIDValidator implements IParameterValidator {
private static final String UUID_REGEX =
"[0-9a-fA-F]{8}(-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}";
@Override
public void validate(String name, String value) throws ParameterException {
if (!isValidUUID(value)) {
throw new ParameterException(
"String parameter " + value + " is not a valid UUID.");
}
}
private boolean isValidUUID(String value) {
return Pattern.compile(UUID_REGEX)
.matcher(value)
.matches();
}
}
Then, we can hook it up with the validateWith attribute of the parameter:
然后,我们可以用参数的validateWith属性来连接它。
@Parameter(
names = { "--customer", "-C" },
validateWith = UUIDValidator.class
)
private String customerId;
If we invoke the command with a non-UUID customer Id, the application exits with a validation failure message:
如果我们用一个非UID的客户ID来调用该命令,应用程序会以验证失败的信息退出。
$ java App --C customer001
String parameter customer001 is not a valid UUID.
10. Sub-Commands
10.子命令
Now that we’ve learned about parameter binding, let’s pull everything together to build our commands.
现在我们已经了解了参数绑定,让我们把所有的东西集中起来,建立我们的命令。
In JCommander, we can support multiple commands, called sub-commands, each with a distinct set of options.
在JCommander中,我们可以支持多个命令,称为子命令,每个命令都有一组不同的选项。
10.1. @Parameters Annotation
10.1.@Parameters 注释
We can use @Parameters to define sub-commands. @Parameters contains the attribute commandNames to identify a command.
我们可以使用@Parameters来定义子命令。@Parameters包含属性commandNames以识别一个命令。
Let’s model submit and fetch as sub-commands:
让我们把submit和fetch作为子命令。
@Parameters(
commandNames = { "submit" },
commandDescription = "Submit usage for a given customer and subscription, " +
"accepts one usage item"
)
class SubmitUsageCommand {
//...
}
@Parameters(
commandNames = { "fetch" },
commandDescription = "Fetch charges for a customer in the current month, " +
"can be itemized or aggregated"
)
class FetchCurrentChargesCommand {
//...
}
JCommander uses the attributes in @Parameters to configure the sub-commands, such as:
JCommander使用@Parameters中的属性来配置子命令,例如。
- commandNames – name of the sub-command; binds the command-line arguments to the class annotated with @Parameters
- commandDescription – documents the purpose of the sub-command
10.2. Adding Sub-Commands to JCommander
10.2.向JCommander添加子命令
We add the sub-commands to JCommander with the addCommand method:
我们用addCommand方法将子命令添加到JCommander。
SubmitUsageCommand submitUsageCmd = new SubmitUsageCommand();
FetchCurrentChargesCommand fetchChargesCmd = new FetchCurrentChargesCommand();
JCommander jc = JCommander.newBuilder()
.addCommand(submitUsageCmd)
.addCommand(fetchChargesCmd)
.build();
The addCommand method registers the sub-commands with their respective names as specified in the commandNames attribute of @Parameters annotation.
addCommand方法用@Parameters注解的commandNames属性中指定的各自名称注册了子命令。
10.3. Parsing Sub-Commands
10.3.解析子命令
To access the user’s choice of command, we must first parse the arguments:
为了访问用户选择的命令,我们必须首先解析参数。
jc.parse(args);
Next, we can extract the sub-command with getParsedCommand:
接下来,我们可以用getParsedCommand提取子命令。
String parsedCmdStr = jc.getParsedCommand();
In addition to identifying the command, JCommander binds the rest of the command-line parameters to their fields in the sub-command. Now, we just have to call the command we want to use:
除了识别命令之外,JCommander还将其余的命令行参数与子命令中的字段绑定。现在,我们只需要调用我们想使用的命令。
switch (parsedCmdStr) {
case "submit":
submitUsageCmd.submit();
break;
case "fetch":
fetchChargesCmd.fetch();
break;
default:
System.err.println("Invalid command: " + parsedCmdStr);
}
11. JCommander Usage Help
11 JCommander使用帮助
We can invoke usage to render a usage guide. This is a summary of all the options that our application consumes. In our application, we can invoke usage on the main command, or alternatively, on each of the two commands “submit” and “fetch” separately.
我们可以调用usage来呈现一个使用指南。这是对我们的应用程序所消耗的所有选项的总结。在我们的应用程序中,我们可以在主命令上调用用法,或者在两个命令 “submit “和 “fetch “中的每一个上分别调用。
A usage display can help us in a couple of ways: showing help options and during error handling.
使用显示可以在几个方面帮助我们:显示帮助选项和在错误处理期间。
11.1. Showing Help Options
11.1.显示帮助选项
We can bind a help option in our commands using a boolean parameter along with the attribute help set to true:
我们可以在我们的命令中使用一个boolean参数和help属性设置为true来绑定一个帮助选项。
@Parameter(names = "--help", help = true)
private boolean help;
Then, we can detect if “–help” has been passed in the arguments, and call usage:
然后,我们可以检测参数中是否传递了”-help”,并调用usage。
if (cmd.help) {
jc.usage();
}
Let’s see the help output for our “submit” sub-command:
让我们看看 “提交 “子命令的帮助输出。
$ java App submit --help
Usage: submit [options]
Options:
* --customer, -C Id of the Customer who's using the services
* --subscription, -S Id of the Subscription that was purchased
* --quantity Used quantity; reported quantity is added over the
billing period
* --pricing-type, -P Pricing type of the usage reported (values: [PRE_RATED,
UNRATED])
* --timestamp Timestamp of the usage event, must lie in the current
billing period
--price If PRE_RATED, unit price to be applied per unit of
usage quantity reported
The usage method uses the @Parameter attributes such as description to display a helpful summary. Parameters marked with an asterisk (*) are mandatory.
usage方法使用@Parameter属性,如description来显示一个有用的摘要。标有星号(*)的参数是强制性的。
11.2. Error Handling
11.2.错误处理
We can catch the ParameterException and call usage to help the user understand why their input was incorrect. ParameterException contains the JCommander instance to display the help:
我们可以捕获ParameterException并调用usage来帮助用户了解为什么他们的输入是错误的。ParameterException包含JCommander实例来显示帮助。
try {
jc.parse(args);
} catch (ParameterException e) {
System.err.println(e.getLocalizedMessage());
jc.usage();
}
12. Conclusion
12.结论
In this tutorial, we used JCommander to build a command-line application. While we covered many of the major features, there’s more in the official documentation.
在本教程中,我们使用JCommander来构建一个命令行应用程序。虽然我们涵盖了许多主要的功能,但在官方的文档中还有更多内容。
As usual, the source code for all the examples is available over on GitHub.
像往常一样,所有例子的源代码都可以在GitHub上找到。