天天看點

使用 JCommander 解析指令行參數

前言

如果你想建構一個支援指令行參數的程式,那麼 jcommander 非常适合你,jcommander 是一個隻有幾十 kb 的 Java 指令行參數解析工具,可以通過注解的方式快速實作指令行參數解析。

這篇教程會通過介紹 jcommadner ,快速的建立一個指令行程式,最後支援的指令參數功能如下圖。

使用 JCommander 解析指令行參數

這個指令行工具仿照 git 操作指令,主要提供了如下功能指令:

  1. 1. git-app.jar -help 檢視指令幫助資訊。
  2. 2. git-app.jar -version 檢視目前版本号。
  3. 3. git-app.jar clone http://xxxx 通過 URL 克隆一個倉庫。
  4. 4. git-app.jar add file1 file2 暫存 file1 檔案 file2 檔案。
  5. 5. git-app.jar commit -m "注釋" 送出并添加注釋。

jcommander 引入

截止文章編寫時間,最新版本如下:

<!-- https://mvnrepository.com/artifact/com.beust/jcommander -->
<dependency>
    <groupId>com.beust</groupId>
    <artifactId>jcommander</artifactId>
    <version>1.82</version>
</dependency>           

jcommander 參數綁定

指令行解析中,參數解析與綁定是最實用的一個場景,jcommander 使用 Parameter 注解進行參數綁定。我們定義一個 GitCommandOptions.java 類來測試參數綁定。

package com.wdbyte.jcommander.v1;

import com.beust.jcommander.Parameter;

/**
 * @author https://www.wdbyte.com
 */
public class GitCommandOptions {
    @Parameter(names = {"clone"},
        description = "克隆遠端倉庫資料")
    private String cloneUrl;

    public String getCloneUrl() {
        return cloneUrl;
    }
}           

使用 jcommander 結合 GitCommandOptions 來解析參數。

package com.wdbyte.jcommander.v1;

import com.beust.jcommander.JCommander;

/**
 * @author https://www.wdbyte.com
 */
public class GitApp {

    public static void main(String[] args) {
            // args = new String[]{"clone","http://www.wdbyte.com/test.git"};
        GitCommandOptions gitCommandOptions = new GitCommandOptions();
        JCommander commander = JCommander.newBuilder()
            .addObject(gitCommandOptions)
            .build();
        commander.parse(args);
        System.out.println("clone " + gitCommandOptions.getCloneUrl());
    }
}           

打包後可以執行指令參數:

$ java -jar git-app.jar clone http://www.wdbyte.com/test.git
clone http://www.wdbyte.com/test.git           

這裡是一個字元串參數,需要在指令中輸出參數值,對于 boolean 類型的參數,不需要傳值,有指令即為 true 值。

參數名稱

@Parameter 注解中的 names 屬性可以定義參數的名稱。且可以指定多個參數名稱,讓我再添加 version 參數和 help 參數,同時設定參數别名。這兩個參數是 boolean 類型。

@Parameter(names = {"help", "-help", "-h"},
    description = "檢視幫助資訊",
    help = true)
private boolean help;

@Parameter(names = {"version", "-version", "-v"},
    description = "顯示目前版本号")
private boolean version = false;           

參數限制

clone 參數可以接受一個要克隆的 URL 連結,但是正常情況下隻需要一個 URL 連結。可以通過 arity = 1 進行限制。

@Parameter(names = {"clone"},
    description = "克隆遠端倉庫資料",
    arity = 1)
private String cloneUrl;           

幫助資訊

使用 usage() 參數可以列印指令幫助資訊。

GitCommandOptions gitCommandOptions = new GitCommandOptions();
JCommander commander = JCommander.newBuilder()
    .addObject(gitCommandOptions)
    .build();
commander.parse(args);
// 列印幫助資訊
commander.usage();           

運作輸出幫助資訊:

$ java -jar git-app.jar
Usage: <main class> [options]
  Options:
    clone
      克隆遠端倉庫資料
    help, -help, -h
      檢視幫助資訊
    version, -version, -v
      顯示目前版本号
      Default: false           

雖然正确的輸出了幫助資訊,但是其中有 main class 這段,是因為我們沒有指定項目名稱,我們指定項目名稱為 git-app。

JCommander commander = JCommander.newBuilder()
            .programName("git-app")
            .addObject(gitCommandOptions)
            .build();           

參數排序

在幫助資訊中,如果想要自定義參數順序,可以通過 order = 來排序,數字越小越靠前。

@Parameter(names = {"version", "-version", "-v"},
    description = "顯示目前版本号",
    order = 2)
private boolean version = false;           

參數綁定完整測試

package com.wdbyte.jcommander.v2;
import com.beust.jcommander.Parameter;
/**
 * @author https://www.wdbyte.com
 */
public class GitCommandOptions {

    @Parameter(names = {"help", "-help", "-h"},
        description = "檢視幫助資訊",
        order = 1,
        help = true)
    private boolean help;

    @Parameter(names = {"clone"},
        description = "克隆遠端倉庫資料",
        order = 3,
        arity = 1)
    private String cloneUrl;

    @Parameter(names = {"version", "-version", "-v"},
        description = "顯示目前版本号",
        order = 2)
    private boolean version = false;
    //...get method
}           

GitApp.java

package com.wdbyte.jcommander.v2;

import com.beust.jcommander.JCommander;

public class GitApp {

    public static void main(String[] args) {
        GitCommandOptions gitCommandOptions = new GitCommandOptions();
        JCommander commander = JCommander.newBuilder()
            .programName("git-app")
            .addObject(gitCommandOptions)
            .build();
        commander.parse(args);
        // 列印幫助資訊
        if (gitCommandOptions.isHelp()) {
            commander.usage();
            return;
        }
        if (gitCommandOptions.isVersion()) {
            System.out.println("git version 2.24.3 (Apple Git-128)");
            return;
        }
        if (gitCommandOptions.getCloneUrl() != null) {
            System.out.println("clone " + gitCommandOptions.getCloneUrl());
        }
    }
}           

運作測試:

使用 JCommander 解析指令行參數

jcommander 參數驗證

在上面的例子中, 假設 clone 指令傳入的參數必須是一個 URL,那麼我們就要進行參數驗證,jcommander 也提供了特有的參數驗證方式。

  1. 1. 編寫參數驗證類,需要實作 IParameterValidator 接口。
  2. package com.wdbyte.jcommander.v3;

    import java.net.MalformedURLException;

    import java.net.URL;

    import com.beust.jcommander.IParameterValidator;

    import com.beust.jcommander.ParameterException;

    /**

    * @author https://www.wdbyte.com

    */

    public class UrlParameterValidator implements IParameterValidator {

    @Override

    public void validate(String key, String value) throws ParameterException {

    try {

    new URL(value);

    } catch (MalformedURLException e) {

    throw new ParameterException("參數 " + key + " 的值必須是 URL 格式");

    }

    }

    }

  3. 2. clone 參數指定驗證類。
  4. @Parameter(names = {"clone"},

    description = "克隆遠端倉庫資料",

    validateWith = UrlParameterValidator.class,

    order = 3,

    arity = 1)

    private String cloneUrl;

運作測試:

$ java -jar git-app.jar clone https://www.wdbyte.com/test.git
clone https://www.wdbyte.com/test.git

$ java -jar git-app.jar clone test.git
Exception in thread "main" com.beust.jcommander.ParameterException: 參數 clone 的值必須是 URL 格式
    at com.wdbyte.jcommander.v3.UrlParameterValidator.validate(UrlParameterValidator.java:19)
    at com.beust.jcommander.ParameterDescription.validateParameter(ParameterDescription.java:377)
    at com.beust.jcommander.ParameterDescription.validateParameter(ParameterDescription.java:344)           

jcommander 子指令

在使用 git 時,我們經常會使用下面兩個指令。

  1. 1. git add file1 file2 暫存 file1 檔案 file2 檔案。
  2. 2. git commit -m "注釋" 送出并添加注釋。

什麼是子指令

這是一種很常見的操作,git commit 除了可以跟 -m 子參數外,還可以跟各種參數,通過 git 幫助文檔可以看到。

git commit [-a | --interactive | --patch] [-s] [-v] [-u<mode>] [--amend]
           [--dry-run] [(-c | -C | --fixup | --squash) <commit>]
           [-F <file> | -m <msg>] [--reset-author] [--allow-empty]
           [--allow-empty-message] [--no-verify] [-e] [--author=<author>]
           [--date=<date>] [--cleanup=<mode>] [--[no-]status]
           [-i | -o] [-S[<keyid>]] [--] [<file>...]           

這種有子參數的情況,我們可以稱 commit 為 git 的一個子指令,使用 jcommander 如何配置子指令呢?

jcommander 子指令實作

我們新增子指令對應的參數類 GitCommandCommit.java.

package com.wdbyte.jcommander;

import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;

/**
 * git commit -m "desc"
 * @author https://www.wdbyte.com
 */
@Parameters(commandDescription = "送出檔案", commandNames = "commit")
public class GitCommandCommit {
  
    public static final String COMMAND = "commit";
  
    @Parameter(names = {"-comment", "-m"},
        description = "請輸入注釋",
        arity = 1,
        required = true)
    private String comment;

    public String getComment() {
        return comment;
    }
}           

代碼中使用 @Parameters 注解指定了子指令為 commit,同時使用 @Paramete 注解指定子參數 -m,同時 -m 參數是必須的,使用屬性 required = true 來指定。

使用 GitCommandCommit:

使用 addCommand 添加 Commit 指令參數類。

GitCommandOptions gitCommandOptions = new GitCommandOptions();
GitCommandCommit commandCommit = new GitCommandCommit();
JCommander commander = JCommander.newBuilder()
    .programName("git-app")
    .addObject(gitCommandOptions)
    .addCommand(commandCommit)
    .build();
commander.parse(args);

String parsedCommand = commander.getParsedCommand();
if ("commit".equals(parsedCommand)) {
    System.out.println(commandCommit.getComment());
}           

運作測試:

$ java -jar git-app.jar commit -m '注釋一下'
注釋一下           

同上,我們可以添加 add 指令對應的參數類:GitCommandAdd.java. 這次我們定義一個 List 類型參數,但是不在屬性上指定子參數名稱。

package com.wdbyte.jcommander.v5;

import java.util.List;

import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;

/**
 * git add file1 file2
 *
 * @author https://www.wdbyte.com
 */
@Parameters(commandDescription = "暫存檔案", commandNames = "add", separators = " ")
public class GitCommandAdd {
    public static final String COMMAND = "add";
    @Parameter(description = "暫存檔案清單")
    private List<String> files;

    public List<String> getFiles() {
        return files;
    }
}           

同樣添加到子指令:

JCommander commander = JCommander.newBuilder()
    .programName("git-app")
    .addObject(gitCommandOptions)
    .addCommand(commandCommit)
    .addCommand(commandAdd)
    .build();
commander.parse(args);
if ("add".equals(parsedCommand)) {
    for (String file : commandAdd.getFiles()) {
        System.out.println("暫存檔案:" + file);
    }
}           

運作測試:

$ java -jar git-app.jar add file1.txt file2.txt
暫存檔案:file1.txt
暫存檔案:file2.txt           

jcommander 參數轉換

在上面的 GitCommandAdd 代碼中,add 指令傳入的都是檔案路徑,現在是使用 List<String> 來接收入參,通常情況想我們可能需要直接轉換成友善操作的類型,如 File 或者 Path,這該如何方面的轉換呢,jcommander 也提供了友善轉換類。

首先編寫一個轉換類 FilePathConverter 用于把入參轉換成 Path 類,同時校驗檔案是否存在

package com.wdbyte.jcommander;

import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

import com.beust.jcommander.IStringConverter;
import com.beust.jcommander.ParameterException;

/**
 *
 * @author https://www.wdbyte.com
 */
public class FilePathConverter implements IStringConverter<Path> {

    @Override
    public Path convert(String filePath) {
        Path path = Paths.get(filePath);
        if (Files.exists(path)) {
            return path;
        }
        throw new ParameterException(String.format("檔案不存在,path:%s", filePath));
    }
}           

通過注解指定轉換類:

@Parameter(description = "暫存檔案清單", converter = FilePathConverter.class)
private List<Path> files;           

打包測試:

$ java -jar git-app.jar add file1 file2
檔案不存在,path:file1

$ ls -l
total 12448
drwxr-xr-x  2 darcy  staff    64B  6 15 21:10 archive-tmp
drwxr-xr-x  3 darcy  staff    96B  6 15 21:10 classes
drwxr-xr-x  3 darcy  staff    96B  6 15 21:10 generated-sources
-rw-r--r--  1 darcy  staff   5.6M  6 16 20:44 git-app.jar

$ git-app.jar git-app.jar
暫存檔案:git-app.jar           

總體測試

使用 JCommander 解析指令行參數

一如既往,文章代碼都存放在 Github.com/niumoo/javaNotes.

原文出自公衆号:程式猿阿朗

原文連結:https://mp.weixin.qq.com/s/-ylT0V6b5sMVABqaG0Q4mw