1. 程式人生 > >[轉載]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介紹

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 設定此引數是否為展示幫助文件或者輔助功能

/**
 * 
 * @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 <[email protected]>
 */
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

PS: 我們一直在招人,Java,杭州,阿里系獨角獸公司,E輪融資,歡迎投簡歷至adasai90Atgmail.com

相關推薦

[轉載]Java利用JCommander開發命令互動(CLI)

有時候我們用Java開發了一個小工具,希望通過命令列(CLI)或者圖形介面直接呼叫。命令列相較於圖形介面,實現迅速,互動更接近於程式設計師人群,本文主要介紹Java在命令列互動上的應用,我們不妨先看看命令列的兩種風格: POSIX風格 tar -zxvf

flink專案開發-flink的scala shell命令互動模式開發

flink的 scala shell命令列互動模式開發 flink帶有一個整合的scala shell命令列。它可以以本地方式啟動來模擬叢集叢集。執行下面的命令就可以通過shell命令列和flink叢集互動(這種方式方便於程式碼除錯): bin/start-scala

Java中eclipse與命令向main函式傳遞引數

  我們知道main函式是java程式的入口,main函式的引數型別是String[]。 1.Eclipse中向main方法傳遞引數 例如: public class Mytest { public static void main(String[] args) {

在android中通過java層程式呼叫命令的一些備註

能呼叫哪些命令? 一般性的, 最常用的命令都能呼叫, 比如cat, cp, top, ls, ps命令, 但用法和linux上的有較大區別, 可通過–help/-h查詢具體的命令用法; 我熟知linux terminal命令列, 但如何知道android都有哪些常用命令呢? 首

IPython 7.2.0 釋出,Python 命令互動

IPython 是 Python 的原生互動式 shell 的增強版,可以完成許多不同尋常的任務,比如幫助實現並行化計算;主要使用它提供的互動性幫助,比如程式碼著色、改進了的命令列回撥、製表符完成、巨集功能以及改進了的互動式幫助。 IPython 7.2.0 帶來了一些小的 bug 修正、改進和新的配

吻逗死(windows)系統下自動部署指令碼(for java spring*)及linux命令工具

轉載請註明出處:https://www.cnblogs.com/funnyzpc/p/10051647.html (^^)(^^)自動部署指令碼原本在上個公司就在使用,由於近期同事需要手動部署一個SpringCloud應用,一邊是sftp軟體上傳,一邊是SourceCRT命令列工具,看這著實很累,就順手把我

Mac/Linux 配置多版本Java,並實現命令快速切換

0. 目標 使用簡單快捷的命令列,快速的切換本機的Java環境 1. 下載JDK 1.8:官網下載; 1.7:https://pan.baidu.com/s/1iCWtZhK_E-KYwZJcMqQ-mQ 2. 安裝JDK 一路next 安裝完成的路徑:

JAVA核心技術I---JAVA基礎知識(命令

一:命令列編譯檔案 手動在c:\temp建立cn.com.test.Man.java –即c:\temp\cn\com\test\Man.java –c:\temp可以替換成任何路徑,後續命令同樣替換 編譯: –X:\>java c:\temp\cn\com\test\Man.java

NetBeans Java程式專案打包 命令執行

將NetBeans中的Java專案打包 並用命令列執行 一、專案打包為jar包 1、Java專案      寫了一個簡單的程式      2、 專案構建       

inquirer.js —— 一個使用者與命令互動的工具

寫在前面: 開始通過npm init 建立package.json的時候就有大量與使用者的互動(當然也可以通過引數來忽略輸入);而現在大多數工程都是通過腳手架來建立的,使用腳手架的時候最明顯的就是與命令列的互動,如果想自己做一個腳手架或者在某些時候要與使用者進

JAVA如何呼叫WINDOWS命令

用Java編寫應用時,有時需要在程式中呼叫另一個現成的可執行程式或系統命令,這時可以通過組合使用Java提供的Runtime類和Process類的方法實現。下面是一種比較典型的程式模式: ... Process process = Runtime.getRuntime().exec(".//p.exe");

linux開發 -- 命令引數解析 getopt

linux大部分工具都是以命令列方式執行,因此都需要對命令列引數解析,它們大多都是用相同的解析方法!(有點廢話)再次記錄下來!省得以後再查。大部分軟體都是用getopt系列函式解析命令列,glibc中就提供了該函式的實現,即使沒有依賴glibc,其他軟體包也會提供相應的實現。

Nodejs 開發命令工具

準備工作 起步 新建一個目錄,作為命令列工具原始碼目錄。 mkdir webflow-cli 初始化 package.json 檔案 npm init --yes 進入命令列工具原始碼目錄(webflow-cli),並且新建 bin

C# 編寫命令互動工具——實時輸出_獲取執行結果

我們在寫程式的時候通常會用到命令列工具。 如Ping 某個網段,寫個登錄檔,啟動項,或者感謝其他壞事。 在網上查了一下,多數都說用C# 做命令列互動需要做很多很多的邏輯處理。那麼今天博主也來寫一個簡單一點的。 首先我們建一個CmdUtils類,然後編寫我們需要的方法 那麼在

golang開發:類庫篇(三)命令工具cli的使用

為什麼要使用命令列 覺得這個問題不應該列出來,又覺得如果初次進行WEB開發的話,可能會覺得所有的東西都可以使用API去做,會覺得命令列沒有必要。 其實,一個生產的專案命令列是繞不過去的。比如運營需要匯出報表、統計下付費使用者、服務不穩定修改下訂單狀態等等,再者,命令列的工具基本都是內部使用,除錯日誌可以隨意點

Flask內建命令工具—CLI

應用發現 flask命令在Flask庫安裝後可使用,使用前需要正確配置FLASK_APP環境變數以告知使用者程式所在位置。不同平臺設定方式有所不同。 Unix Bash (Linux, Mac, etc.): $ export FLASK_APP=hello $ flask run Windows

PHP命令CLI模式)

CLI模式 CLI模式其實就是命令列執行模式,英文全稱Command-Line Interface(命令列介面) $ php -h Usage: php [options] [-f] <file> [--] [args...] php [options] -r <code&g

commanderJs編寫命令工具(cli)

前言:   最近需要做一個內部的node cli來獨立構建流程,對整個命令列工具實現流程有了大致瞭解,下面來解釋一下如何實現一個cli,和如何使用  commander 庫。   新手誤區:   在開始實現之前,我知道有  commander

php命令cli)下執行PHP指令碼時相對路徑報錯問題

問題產生的原因: 在php命令列下執行.php檔案時,執行環境的工作目錄是php命令程式(php.exe)所在目錄,所以如果想在檔案內使用相對路徑時,要先切換當前的工作目錄才行。 解決方法:將工作目錄切換到當前檔案目錄 $oldpath = getcwd();

MacOS High Sierra 10.13.6上安裝LeanCloud命令工具CLI

簡介 根據本人分析(供參考),LeanCloud和mbmod是時下國內非常流行的兩家雲API供應商,其主要市場瞄準移動開發。有關細節在此不贅述,有興趣的朋友可以參考官方網站有關資料。本文介紹在MacOS High Sierra 10.13.6上安裝LeanCloud命令列工具CLI的注意事項。 命令列