假设我们要写一个命令行界面的天气查询程序,它的命令行参数列表如下:
代码如下:
public class App { private static final Options OPTIONS = new Options(); //配置Options static { //设置每个Option Option city = OptionBuilder.withArgName("city").isRequired().hasArg() .withDescription("要查询的城市名称").withLongOpt("city").create('c'); Option day = OptionBuilder.withArgName("day").hasArg() .withDescription("要查询的日期: 0表示当天,1表示第二天, 2表示第三天,依次类推") .withLongOpt("day").create('d'); Option help = OptionBuilder.withDescription("显示帮助信息") .withLongOpt("help").create('h'); OPTIONS.addOption(city); OPTIONS.addOption(day); OPTIONS.addOption(help); } public static void main(String[] args) throws Exception { try { //解析参数 CommandLine cli = new BasicParser().parse(OPTIONS, args); if (cli.hasOption('h')) { new HelpFormatter().printHelp("java -jar weather.jar [OPTIONS]", OPTIONS, false); return; } String city = cli.getOptionValue('c'); String day = cli.getOptionValue('d'); System.out.println(Wheather.get(city, day == null ? 0 : Integer.parseInt(day))); } catch (Exception e) { new HelpFormatter().printHelp("java -jar weather.jar [OPTIONS]", OPTIONS, false); } } }
可以看到代码的可读性不太好,而且如果有多个命令行参数时需要编写多个Options, 这会使App类变得臃肿。
我们可以使用注解来对其进行简化
1.定义CLIOptions注解,用于标注要解析的参数类
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface CLIOptions { }
2.定义CLIOptoin注解,用于标注和解释命令行参数的属性
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface CLIOption { public boolean hasArg() default false; public boolean hasArgs() default false; public boolean isRequired() default false; public String opt() default ""; public String longOpt() default ""; public String desc() default ""; public String argName() default ""; }
3.定义注解的处理器,用于生成Options实例
public class CLIOptionsProcessor { public static Options process(Class<?> cliClz) { if (cliClz.getAnnotation(CLIOptions.class) == null) throw new RuntimeException(String.format( "Target class: %s is not annotated with CLIOptions", cliClz.getCanonicalName())); Options options = new Options(); CLIOption cli = null; for (Field field : cliClz.getDeclaredFields()) { cli = field.getAnnotation(CLIOption.class); if (cli != null) options.addOption(parseOption(field, cli)); } return options; } private static Option parseOption(Field field, CLIOption cli) { if (cli.hasArg()) OptionBuilder.hasArg(); if (cli.hasArgs()) OptionBuilder.hasArgs(); if (cli.isRequired()) OptionBuilder.isRequired(); if (!cli.desc().isEmpty()) OptionBuilder.withDescription(cli.desc()); if (!cli.argName().isEmpty()) OptionBuilder.withArgName(cli.argName()); if (cli.longOpt().isEmpty()) OptionBuilder.withLongOpt(field.getName()); else OptionBuilder.withLongOpt(cli.longOpt()); return OptionBuilder.create(cli.opt()); } }
OK, 我们使用新添加的注解来对这个程序进行简化,代码如下:
@CLIOptions public class App { @CLIOption(argName = "city", isRequired = true, hasArg = true, desc = "要查询的城市名称", longOpt = "city", opt = "c") private String city; @CLIOption(argName = "day", hasArg = true, desc = "要查询的日期: 0表示当天,1表示第二天, 2表示第三天,依次类推", longOpt = "day", opt = "d") private int day; @CLIOption(desc = "显示帮助信息", longOpt = "help", opt = "h") private boolean help; public static void main(String[] args) throws Exception { App app = new App(); Options options = CLIOptionsProcessor.process(App.class); try { CommandLine cli = new BasicParser().parse(options, args); app.help = cli.hasOption('h'); if (app.help) { app.showHelp(options); return; } app.city = cli.getOptionValue('c'); String tmp = cli.getOptionValue('d'); app.day = tmp == null ? 0 : Integer.parseInt(tmp); } catch (Exception e) { app.showHelp(options); } System.out.println(Wheather.get(app.city, app.day)); } public void showHelp(Options options) { new HelpFormatter().printHelp("java -jar weather.jar [OPTIONS]", options, false); } }