目录

转载Java利用JCommander开发命令行交互CLI

[转载]Java利用JCommander开发命令行交互(CLI)

转载,原文地址:

有时候我们用Java开发了一个小工具,希望通过命令行(CLI)或者图形界面直接调用。命令行相较于图形界面,实现迅速,交互更接近于程序员人群,本文主要介绍Java在命令行交互上的应用,我们不妨先看看命令行的两种风格:

  • POSIX风格 tar -zxvf foo.tar.gz
  • Java风格 java -Djava.awt.headless=true -Djava.net.useSystemProxies=true Foo

JCommander介绍

是Java解析命令行参数的工具,作者是 ,他的开源测试框架testNG相信很多程序员都有耳闻。

根据官方文档,我简单总结了JCommander的几个特点:

  • 注解驱动

    它的核心功能 命令行参数定义 是基于注解的,这也是我选择用它的主要原因。我们可以轻松做到命令行参数与属性的映射,属性除了是String类型,还可以是Integer、boolean,甚至是File、集合类型。

  • 功能丰富

    它同时支持文章开头的两种命令行风格,并且提供了输出帮助文档的能力( usage() ),还提供了国际化的支持。

  • 高度扩展

    下文会详述。

在看具体应用示例前,我们先读懂核心注解 @Parameter 的源码(你大可以跳过下面这段长长的源码,直接看示例),以此来了解它向我们展示了哪些方面的能力:

@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
@Target({ FIELD, METHOD })
public @interface Parameter {

/**

- An array of allowed command line parameters (e.g. "-d", "--outputdir", etc...).
- If this attribute is omitted, the field it's annotating will receive all the
- unparsed options. There can only be at most one such annotation.
  */
  String[] names() default {};

/**

- A description of this option.
  */
  String description() default "";

/**

- Whether this option is required.
  */
  boolean required() default false;

/**

- The key used to find the string in the message bundle.
  */
  String descriptionKey() default "";

/**

- How many parameter values this parameter will consume. For example,
- an arity of 2 will allow "-pair value1 value2".
  */
  public static int DEFAULT_ARITY = -1;
  int arity() default DEFAULT_ARITY;

/**

- If true, this parameter is a password and it will be prompted on the console
- (if available).
  */
  boolean password() default false;

/**

- The string converter to use for this field. If the field is of type <tt>List</tt>
- and not <tt>listConverter</tt> attribute was specified, JCommander will split
- the input in individual values and convert each of them separately.
  */
  Class<? extends IStringConverter<?>> converter() default NoConverter.class;

/**

- The list string converter to use for this field. If it's specified, the
- field has to be of type <tt>List</tt> and the converter needs to return
- a List that's compatible with that type.
  */
  Class<? extends IStringConverter<?>> listConverter() default NoConverter.class;

/**

- If true, this parameter won't appear in the usage().
  */
  boolean hidden() default false;

/**

- Validate the parameter found on the command line.
  */
  Class<? extends IParameterValidator>[] validateWith() default NoValidator.class;

/**

- Validate the value for this parameter.
  */
  Class<? extends IValueValidator>[] validateValueWith() default NoValueValidator.class;

/**
_ @return true if this parameter has a variable arity. See @{IVariableArity}
_/
boolean variableArity() default false;

/**

- What splitter to use (applicable only on fields of type <tt>List</tt>). By default,
- a comma separated splitter will be used.
  */
  Class<? extends IParameterSplitter> splitter() default CommaParameterSplitter.class;

/**

- If true, console will not echo typed input
- Used in conjunction with password = true
  */
  boolean echoInput() default false;

/**

- If true, this parameter is for help. If such a parameter is specified,
- required parameters are no longer checked for their presence.
  */
  boolean help() default false;

/**

- If true, this parameter can be overwritten through a file or another appearance of the parameter
  _ @return nc
  _/
  boolean forceNonOverwritable() default false;

/**

- If specified, this number will be used to order the description of this parameter when usage() is invoked.
  _ @return
  _/
  int order() default -1;

}

JCommander 应用示例

在一般应用场景,我们可能只需要设置 @Parameter 以下几个属性值:

_ names 设置命令行参数,如 -old

_ required 设置此参数是否必须

_ description 设置参数的描述

_ order 设置帮助文档的顺序

  • help 设置此参数是否为展示帮助文档或者辅助功能

下面是一个完整的示例,它用来比较两份文档,然后输出差异。源码在 上。

/**

- _ @author Sayi
  _ @version
  */
  public class CLI {

private static final String OUTPUT_MODE_MARKDOWN = "markdown";

@Parameter(names = "-old", description = "old api-doc location:Json file path or Http url", required = true, order = 0)
private String oldSpec;

@Parameter(names = "-new", description = "new api-doc location:Json file path or Http url", required = true, order = 1)
private String newSpec;

@Parameter(names = "-v", description = "swagger version:1.0 or 2.0", validateWith = RegexValidator.class, order = 2)
@Regex("(2\0|1\0)")
private String version = SwaggerDiff.SWAGGER_VERSION_V2;

@Parameter(names = "-output-mode", description = "render mode: markdown or html", validateWith = RegexValidator.class, order = 3)
@Regex("(markdown|html)")
private String outputMode = OUTPUT_MODE_MARKDOWN;

@Parameter(names = "--help", help = true, order = 5)
private boolean help;

@Parameter(names = "--version", description = "swagger-diff tool version", help = true, order = 6)
private boolean v;

public static void main(String[] args) {
CLI cli = new CLI();
JCommander jCommander = JCommander.newBuilder().addObject(cli).build();
jCommander.parse(args);
cli.run(jCommander);
}

public void run(JCommander jCommander) {
if (help) {
jCommander.setProgramName("java -jar swagger-diff.jar");
jCommander.usage();
return;
}
if (v) {
JCommander.getConsole().println("1.2.0");
return;
}

    //SwaggerDiff diff = null;

}
}

运行命令行查看帮助文档,输出结果如下:

$ java -jar swagger-diff.jar --help
Usage: java -jar swagger-diff.jar [options]
Options:

- -old
  old api-doc location:Json file path or Http url
- -new
  new api-doc location:Json file path or Http url
  -v
  swagger version:1.0 or 2.0
  Default: 2.0
  -output-mode
  render mode: markdown or html
  Default: markdown
  --help

  --version
  swagger-diff tool version

这个示例像我们展示了JCommander注解的强大,我们仅仅使用注解就完成了所有参数的定义。注意,对于boolean为true的参数,我们只需要输入参数名,比如 --help ,而不是 --help=true

示例中使用了 usage() 方法即可完美的输出帮助文档。

JCommander扩展:增加正则表达式校验

JCommander是高度扩展的,两个核心接口定义了扩展的能力。

IStringConverter 支持String类型的参数值可以转化为任意其他类型的属性。

/**

- An interface that converts strings to any arbitrary type.
-
- If your class implements a constructor that takes a String, this
- constructor will be used to instantiate your converter and the
- parameter will receive the name of the option that's being parsed,
- which can be useful to issue a more useful error message if the
- conversion fails.
-
- You can also extend BaseConverter to make your life easier.
- _ @author cbeust
  _/
  public interface IStringConverter<T> {
  /**
  _ @return an object of type <T> created from the parameter value.
  _/
  T convert(String value);
  }

IParameterValidator 支持参数值的校验。

/**
- The class used to validate parameters.
- _ @author Cedric Beust <cedric@beust.com>
  _/
  public interface IParameterValidator {

  /**

  - Validate the parameter.
  - _ @param name The name of the parameter (e.g. "-host").
    _ @param value The value of the parameter that we need to validate
  - _ @throws ParameterException Thrown if the value of the parameter is invalid.
    _/
    void validate(String name, String value) throws ParameterException;

}

在阅读上文示例中,可能会有些许疑问,比如 @Regex 是什么注解,JCommander 并没有提供正则表达式校验参数值的功能。

对于很多参数,我们都有校验的场景,比如值只能是几个可选值,或者是在一定范围内,IParameterValidator 和 IParameterValidator2 实现了参数校验了功能,接下来我们将基于接口 IParameterValidator2 扩展 JCommander,同样,我们只需要使用 注解 即可。

  1. 自定义正则注解,这样我们就可以在需要正则校验的属性上,设置表达式,如 @Regex("(2\0|1\0)")
package com.deepoove.swagger.diff.cli;

import static java.lang.annotation.ElementType.FIELD;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;

@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
@Target({ FIELD })
public @interface Regex {

String value() default "";

}
  1. 实现 RegexValidator ,当有 Regex 注解的时候,解析正则表达式,应用校验规则。注意 这段代码使用了反射,可能并不是最优雅的方式,但是在不修改 JCommander 源码的情况下,可能是最好的方式了
package com.deepoove.swagger.diff.cli;

import java.lang.reflect.Field;
import java.util.regex.Pattern;

import com.beust.jcommander.IParameterValidator2;
import com.beust.jcommander.ParameterDescription;
import com.beust.jcommander.ParameterException;
import com.beust.jcommander.Parameterized;

public class RegexValidator implements IParameterValidator2 {

private static final String PARAMETERIZED_FIELD_NAME = "field";

@Override
public void validate(String name, String value) throws ParameterException {
return;
}

@Override
public void validate(String name, String value, ParameterDescription pd)
throws ParameterException {
Parameterized parameterized = pd.getParameterized();
Class<? extends Parameterized> clazz = parameterized.getClass();
try {
Field declaredField = clazz.getDeclaredField(PARAMETERIZED_FIELD_NAME);
declaredField.setAccessible(true);
Field paramField = (Field) declaredField.get(parameterized);
Regex regex = paramField.getAnnotation(Regex.class);
if (null == regex) return;
String regexStr = regex.value();
if (!Pattern.matches(regexStr, value)) { throw new ParameterException(
"Parameter " + name + " should match " + regexStr + " (found " + value + ")"); }
} catch (NoSuchFieldException e) {
return;
} catch (IllegalArgumentException e) {
return;
} catch (IllegalAccessException e) {
return;
}
}
}
  1. 使用正则注解和正则校验类
@Parameter(names = "-v", validateWith = RegexValidator.class)
@Regex("(2\0|1\0)")
private String version = "2.0";

至此,正则校验已完成。

更多 More: Apache Commons CLI

从源码中可以看到,JCommander 默认提供了不少转化器。

----IStringConverter
\--BaseConverter
--\--BigDecimalConverter
--\--BooleanConverter
--\--DoubleConverter
--\--FloatConverter
--\--IntegerConverter
--\--ISO8601DateConverter
--\--LongConverter
--\--PathConverter
--\--URIConverter
--\--URLConverter
\--EnumConverter
\--InetAddressConverter
\--FileConverter

Java 在命令行交互的应用,还有很多工具。另一个使用比较广泛的是 ,它比 JCommander 支持更多的命令行风格,但是扩展能力不够。

PS: 我们一直在招人,Java,杭州,阿里系独角兽公司,E 轮融资,欢迎投简历至 adasai90Atgmail.com