天天看点

Java 命令行参数解析方式探索(二):Apache Commons

作者:冰心de小屋

使用 Apache Commons CLI 工具包可以更加规范的处理命令行参数,同时支持多种参数风格:

  • POSIX:tar -zxvf foo.tar.gz
  • GNU:du --human-readable --max-depth=1
  • Java:java -Djava.awt.headless=true -Djava.net.useSystemProxies=true Foo
  • 其他:gcc -O2 foo.c ant -projecthelp

该工具处理命令行参数可分为三个阶段:定义阶段、解析阶段和服务阶段。

1. 定义阶段

定义待解析的命令行参数,设置命令行参数的限定属性,例如名称、是否必须、类型和描述信息等。

Java 命令行参数解析方式探索(二):Apache Commons

你可以通过创建 Option 类的实例来定义命令行参数,创建 Option 类的实例有多种途径:

Options options = new Options();


// 方式1:通过调用Options提供的方法
options.addOption("t", "thread",true, "并发数");


// 方式2:通过创建Option类的实例
options.addOption(new Option("c", "count", true, "调用次数"));


// 方式3:通过Option内部类Builder使用流式方式构建
options.addOption(Option.builder("s")
        .longOpt("second")
        .required()
        .hasArg()
        .desc("调用时长:单位秒")
        .build());

           

复制代码

推荐使用流式方式构建 Option,直观流畅:

package com.ice.impl;


import com.ice.Parameter;
import com.ice.Starter;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;


public class CommonCLIStarter extends Starter {
    private static final String THREAD_NAME = "t";
    private static final String COUNT_NAME = "c";
    private static final String SECOND_NAME = "s";
    private static final String PROPERTY_NAME = "p";
    private static final String OUTPUT_NAME = "o";


    public CommonCLIStarter(String[] args) {
        super(args);
    }


    @Override
    protected Parameter parse() {
        // 1. 定义阶段
        Options options = stage1();


        // 2. 解析阶段
        CommandLine commandLine = stage2(options);


        // 3. 服务阶段
        return stage3(commandLine);
    }


    private Options stage1() {
        return new Options()
                .addOption(Option.builder(THREAD_NAME)
                        .longOpt("thread")
                        .required()
                        .hasArg()
                        .desc("并发数")
                        .build())
                .addOption(Option.builder(COUNT_NAME)
                        .longOpt("count")
                        .required()
                        .hasArg()
                        .desc("调用次数")
                        .build())
                .addOption(Option.builder(SECOND_NAME)
                        .longOpt("second")
                        .required().hasArg()
                        .desc("调用时长:单位秒")
                        .build())
                .addOption(Option.builder(PROPERTY_NAME)
                        .longOpt("property")
                        .hasArgs()
                        .valueSeparator()
                        .numberOfArgs(2)
                        .desc("自定义扩展属性")
                        .build())
                .addOption(Option.builder(OUTPUT_NAME)
                        .longOpt("output")
                        .hasArg()
                        .desc("结果输出到文件中")
                        .build());
    }


    private CommandLine stage2(Options options) {
        return null;
    }


    private Parameter stage3(CommandLine commandLine) {
        return null;
    }
}

           

复制代码

2. 解析阶段

命令行参数定义完毕,你需要使用默认的解析器 DefaultParser 类来解析实际的 args 数组,对于 DefaultParser 类主要的工作内容如下:

  1. 通过访问者模式处理 args 数组元素,验证元素的合法性,校验参数的类型是标识符还是具体值等;
  2. 校验必要参数是否缺失;
  3. 参数默认值的设置。
Java 命令行参数解析方式探索(二):Apache Commons

继续编写解析阶段的代码:

private CommandLine stage2(Options options) {
    CommandLineParser parser = new DefaultParser();


    try {
        return parser.parse(options, args);
    } catch (Exception e) {
        throw new IllegalArgumentException("Parsed parameters error", e);
    }
}

           

复制代码

3. 服务阶段

通过解析阶段返回的 CommandLine 类的实例来访问命令行参数实际的值:

private Parameter stage3(CommandLine commandLine) {
    PlanParameter parameter = new PlanParameter();
    parameter.setThread(Integer.parseInt(commandLine.getOptionValue(THREAD_NAME)));
    parameter.setCount(Integer.parseInt(commandLine.getOptionValue(COUNT_NAME)));
    parameter.setSecond(Integer.parseInt(commandLine.getOptionValue(SECOND_NAME)));
    parameter.setOutput(commandLine.getOptionValue(OUTPUT_NAME));


    if (commandLine.hasOption(PROPERTY_NAME)) {
        Properties properties = commandLine.getOptionProperties(PROPERTY_NAME);
        if (properties != null && properties.size() > 0) {
            Map<String, String> property = new HashMap<>();
            parameter.setProperty(property);
            for (Map.Entry<Object, Object> entity : properties.entrySet()) {
                property.put(String.valueOf(entity.getKey()), String.valueOf(entity.getValue()));
            }
        }
    }


    return parameter;
}

           

复制代码

三个阶段的涉及代码编写完毕,让我们来编写单元测试用例,验证结果的正确性:

package com.ice;


import com.ice.impl.CommonCLIStarter;
import org.junit.Test;


public class CLIParameterTest extends ParameterTest {
    @Test
    @Override
    public void startTest() {
        Starter starter = new CommonCLIStarter(args);
        Parameter parameter = starter.parse();
        validate(parameter);
    }
}

           

复制代码

单元测试通过:

Java 命令行参数解析方式探索(二):Apache Commons