如果你想構建一個支援命令列引數的程式,那麼 jcommander 非常適合你,jcommander 是一個只有幾十 kb 的 Java 命令列引數解析工具,可以通過註解的方式快速實現命令列引數解析。
這篇教學會通過介紹 jcommadner ,快速的建立一個命令列程式,最後支援的命令引數功能如下圖。
這個命令列工具仿照 git 操作命令,主要提供瞭如下功能命令:
git-app.jar -help
檢視命令幫助資訊。git-app.jar -version
檢視當前版本號。git-app.jar clone http://xxxx
通過 URL 克隆一個倉庫。git-app.jar add file1 file2
暫存 file1 檔案 file2 檔案。git-app.jar commit -m "註釋"
提交併新增註釋。截止文章編寫時間,最新版本如下:
<!-- https://mvnrepository.com/artifact/com.beust/jcommander -->
<dependency>
<groupId>com.beust</groupId>
<artifactId>jcommander</artifactId>
<version>1.82</version>
</dependency>
命令列解析中,引數解析與繫結是最實用的一個場景,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());
}
}
}
執行測試:
在上面的例子中, 假設 clone 命令傳入的引數必須是一個 URL,那麼我們就要進行引數驗證,jcommander 也提供了特有的引數驗證方式。
編寫引數驗證類,需要實現 IParameterValidator
介面。
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 格式");
}
}
}
clone
引數指定驗證類。
@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)
在使用 git 時,我們經常會使用下面兩個命令。
git add file1 file2
暫存 file1 檔案 file2 檔案。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 如何設定子命令呢?
我們新增子命令對應的引數類 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
在上面的 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
一如既往,文章程式碼都存放在 Github.com/niumoo/javaNotes.
本文 Github.com/niumoo/JavaNotes倉庫已經收錄。
本文原發於網站:https://www.wdbyte.com/tool/jcommander/
我的公眾號:使用 JCommander 解析命令列引數