[From] https://github.com/Sayi/sayi.github.com/issues/32

我喜欢简单,什么是简单?正如若干字符组成的命令行。

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

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

JCommander介绍

JCommander是Java解析命令行参数的工具,作者是cbeust,他的开源测试框架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 设置此参数是否为展示帮助文档或者辅助功能

下面是一个完整的示例,它用来比较两份文档,然后输出差异。源码在https://github.com/Sayi/swagger-diff上。

/**
*
* @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在命令行交互的应用,还有很多工具。另一个使用比较广泛的是Apache Commons CLI: http://commons.apache.org/proper/commons-cli/index.html,它比JCommander支持更多的命令行风格,但是扩展能力不够。

https://github.com/Sayi/sayi.github.com/issues/32

[转] Java 命令行交互-JCommander的更多相关文章

  1. JavaScript实现命令行交互

    原文地址: http://www.cnblogs.com/liaoyu/p/js-terminal.html 周末闲着想试试用 JavaScript 模拟命令行交互的功能,希望达到的几个功能点如下: ...

  2. libvirt 命令行交互工具之virsh

    libvirt是当前主流VM最低层库.IBM PowerVM也不例外,libvirt是深入玩虚拟化必须玩转的东西; 简单测试玩玩libvirt 的virsh命令行交互工具, 你我都知libvirt大体 ...

  3. Linux java 命令行编译 jar包

    Java 命令行编译成class,然后在打包成jar文件. 编译成class javac -classpath $CLASS_PATH -d class ./src/Hello.java 可以通过ja ...

  4. java命令行执行程序解决依赖外部jar包的问题

    用java命令行直接执行程序,如果这个程序需要引用外部jar包.就不能单纯用java xx来执行 如果你的jar包和程序就在一个目录: 编译 javac -cp D:\yy\yy.jar,D\xx\x ...

  5. java命令行打war

    java命令行打war(windows下) 切换到需要打包文件夹low的上级目录>jar -cfM legendwealth.war -C low .

  6. Java 命令行编译项目

    如果是用Exlipse, 第三方的包可以放在eclipse文件夹的jre包的lib文件夹中! (初学者的一些总结-高手们勿喷哈-) 原因: 以前一直用Eclispe编程环境运行Java.非常舒服,就像 ...

  7. Java命令行的基本编译运行

    1.编译 编写MyProgram.java文件,内容如下: public class MyProgram { public static void main(String[] args) { Syst ...

  8. java命令行从编译到打jar包到执行

     目录: 一. javac编译     1. 没有额外的jar包     2. 包含额外的jar包 二. jar打jar包 三. java运行     1. java命令执行     2. jar包执 ...

  9. Java - Java 命令行简介: 选项, 属性, 参数

    概述 简单介绍一下 java 命令行相关的参数及属性 1. java 命令行 基本 命令 > java <mainClass> 描述 执行 Java 类 需要准备好编译完成的 mai ...

随机推荐

  1. Go 面向对象概念

    前言: 本文是学习<<go语言程序设计>> -- 清华大学出版社(王鹏 编著) 的2014年1月第一版 做的一些笔记 , 如有侵权, 请告知笔者, 将在24小时内删除, 转载请 ...

  2. ComponentSpace SAML v2.0 for .NET 使用介绍

    下载地址:http://samlsso.codeplex.com/ 以下描叙参考版本为其官网最新版本2.5.0.6.相对2.4版本,2.5有了很大改进,很多接口方法都变了.使用起来更方便,易懂. 广告 ...

  3. Linux daemon与service 学习笔记

    service 常驻在内存中的进程,且可以提供一些系统或网络功能,就是服务.   daemon service的提供需要进程的运行,所以实现service的程序我们称为daemon.   eg: 实现 ...

  4. VC6.0 如何显示代码行号

    VC6.0是一款比较经典.稳定的功能强大的IDE,目前也有很多人在使用.但美中不足的是它不能像其他IDE那样显示行号.这里需要用到一个插件VC6LineNumberAddin, 下载地址:http:/ ...

  5. Android-ListView-(BaseAdapter初步)

    在Android中就提供了专门列表显示条目的控件,ListView控件,ListView控件不是一次性加载全部数据,他是只加载用户在屏幕看得到的数据,当用户滑动的过程中在去加载新的数据,同时会自动销毁 ...

  6. google chrome 调试技巧:监控 DOM 元素被修改

    在很多时候, 页面上一个元素的属于被修改.删除,子节点的添加与修改,很难一下找到对应的代码,在 google chrome 开发者工具里, 提供了对 DOM 元素的监控: 在 Elements 标签, ...

  7. sql获取表的所有字段及属性

    select c.name as name,t.name as type ,convert(bit,c.IsNullable) as isNULL , from sysobjects where xt ...

  8. K8s集群安装--最新版 Kubernetes 1.14.1

    K8s集群安装--最新版 Kubernetes 1.14.1 前言 网上有很多关于k8s安装的文章,但是我参照一些文章安装时碰到了不少坑.今天终于安装好了,故将一些关键点写下来与大家共享. 我安装是基 ...

  9. mongodb 片键

    mongodb  片键 mongodb的片键是很难控制的,没有完美的片键,只能均衡即可: 片键的方案: 1.id的hashed: 作为第一个方案,你可以使用数据文档_id的哈希作为片键. 这个方案能够 ...

  10. python 和pycharm 安装

    昨天 我重新装了一个Windows 7 系统 结果很多东西丢了 没有做好备份 其中就有python 和pycharm 今天花了一天时间装 想想也是够了 坑真多 整理一下吧 python 网址:http ...