• 使用 JCommander 解析命令行参数


    前言

    如果你想构建一个支持命令行参数的程序,那么 jcommander 非常适合你,jcommander 是一个只有几十 kb 的 Java 命令行参数解析工具,可以通过注解的方式快速实现命令行参数解析。

    这篇教程会通过介绍 jcommadner ,快速的创建一个命令行程序,最后支持的命令参数功能如下图。

    这个命令行工具仿照 git 操作命令,主要提供了如下功能命令:

    1. git-app.jar -help 查看命令帮助信息。
    2. git-app.jar -version 查看当前版本号。
    3. git-app.jar clone http://xxxx 通过 URL 克隆一个仓库。
    4. git-app.jar add file1 file2 暂存 file1 文件 file2 文件。
    5. git-app.jar commit -m "注释" 提交并添加注释。

    jcommander 引入

    截止文章编写时间,最新版本如下:

    
    <dependency>
        <groupId>com.beustgroupId>
        <artifactId>jcommanderartifactId>
        <version>1.82version>
    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: 
    [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 参数验证

    在上面的例子中, 假设 clone 命令传入的参数必须是一个 URL,那么我们就要进行参数验证,jcommander 也提供了特有的参数验证方式。

    1. 编写参数验证类,需要实现 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 格式");
              }
          }
      }
      
    2. 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)
    

    jcommander 子命令

    在使用 git 时,我们经常会使用下面两个命令。

    1. git add file1 file2 暂存 file1 文件 file2 文件。
    2. git commit -m "注释" 提交并添加注释。

    什么是子命令

    这是一种很常见的操作,git commit 除了可以跟 -m 子参数外,还可以跟各种参数,通过 git 帮助文档可以看到。

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

    这种有子参数的情况,我们可以称 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 files;
    
        public List 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 来接收入参,通常情况想我们可能需要直接转换成方便操作的类型,如 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 {
    
        @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 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.

    参考:https://jcommander.org/

    本文 Github.com/niumoo/JavaNotes仓库已经收录。
    本文原发于网站:https://www.wdbyte.com/tool/jcommander/
    我的公众号:使用 JCommander 解析命令行参数

  • 相关阅读:
    android Studio为项目生成签名
    【小海实习日记】golang-iris框架学习笔记
    120. 如何根据 SAP UI5 框架代码抛出的错误消息,反查出是哪一行代码引起的错误消息
    《Java基础知识》Java ArrayList源码分析4
    你不知道的类与对象冷知识
    react学习(三——实战项目)
    Flutter系列文章-实战项目
    【从Paxos到Zookeeper分布式一致性原理与实践】笔记
    JVM相关知识
    【项目管理】Java OCR实现图片文字识别
  • 原文地址:https://www.cnblogs.com/niumoo/p/17515736.html