其實在 Web 上,同樣的問題也存在,就是怎麼把 URL 對應到特定的 servlet, action 或 controller 的方法上。這不管是在 Spring Framework 3 Web MVC, Servlet 3 或是 Jessey 中,都有一套近似的解法,就是使用 @Annotation。
OK, 那我們何不如法泡製,也用 @Annotation 來處理命令列參數呢? 我設計的命令列規範如下:
- 將命令列參數區分成「選項」及「選項參數」。選項如 -start, -stop;選項參數如 -start localhost 18080 中,未加 dash 符號的字串,即紅字部分。
- 一個命令列可以接受多個選項及多個選項參數。
- 支援將選項綁定到主程式裡面的方法,以下我稱之為「選項常式」。
- 支援將選項參數綁定到選項常式的參數上。
- 支援自動列出所有選項及其說明。
- 自動檢查選項參數是否正確。
- 支選整數型態的選項參數。
package com.isong.cmdexe; /** * Usage example:<pre> * java -jar ds.jar -start localhost 2011 * java -jar ds.jar -start localhost 2011 -sm * java -jar ds.jar -stop * </pre> * @author edwardsayer */ public class DummyServer { /** * @param args */ public static void main(String[] argv) { new CmdExecutor(new DummyServer()).exec(argv); } // execute: java -jar ds.jar -start localhost 2011 @CommandArg(value = "-start ip port", desc = "start the server instance.") public void doStart(String ip, int port){ System.out.println("Starting server at " + ip + ":" + port + "..."); } // execute: java -jar ds.jar -sm @CommandArg(value = "-sm", desc = "start session monitor.") public void startSessionMonitor(){ System.out.println("Starting session service..."); } // execute: java -jar ds.jar -stop @CommandArg(value = "-stop", desc = "stop the server instance.") public void doStop(){ System.out.println("Stoping..."); } }
從以上的程式碼片段,可以知道這組 Command line API 提供了一個 CmdExecutor 類別, 以及一個 @CommandArg annotation。
@CommandArg 會在主程式的選項常式上,加上標注訊息,包括選項名稱,以及選項描述。
而CmdExecutor 類別則依據 @CommandArg 所提供的資訊,綁定使用者輸入的參數。另外,若使用者未輸入任何參數,CmdExecutor 則顯示參數提示訊息,這些訊息,其實是我們透過 @CommandArg 加諸在每個選項常式上的訊息。範例如下:
Available options: -start ip port start the server instance. -sm start session monitor. -stop stop the server instance.
接下來,就來了解 CmdExecutor 及 @CommandArg 的實作方式。先看看 @CommandArg 的程式碼:
@Retention(RetentionPolicy.RUNTIME) public @interface CommandArg { String value(); String desc() default ""; }
可以看到 @CommandArg 包含兩個屬性。value 用來設定選項名稱,而 desc 用來設定選項描述。
CmdExecutor 較為冗長,程式碼如下:
public class CmdExecutor { private MapcmdMap; private StringBuffer cmdDocs; private Object target; public CmdExecutor(Object target){ this.target = target; } public int exec(String[] args){ System.out.println("args = " + Arrays.asList(args)); initCommands(); if (args.length == 0) { showCommands(); return 0; } for (int i = 0; i < args.length; i++) { if (args[i].startsWith("-")) { String option = args[i]; Method method = cmdMap.get(option); if (method == null) { System.out.println("Unknown options: " + option); showCommands(); return 1; } try { Class[] types = method.getParameterTypes(); Object[] paras = new Object[types.length]; for (int j = 0; j < types.length; j++) { if ( ++i >= args.length || args[i].startsWith("-")) { System.out.println("Invalid option parameter count: " + option); showCommands(); return 1; } if(types[j].isAssignableFrom(String.class)) paras[j] = args[i]; else if(types[j].isAssignableFrom(int.class) || types[j].isAssignableFrom(Integer.class)) paras[j] = Integer.valueOf(args[i]); else throw new IllegalArgumentException("Invalid command parameter type: " + method.getName()); } method.invoke(target, paras); } catch (Exception e) { e.printStackTrace(); } } } return 0; } private void initCommands(){ if (cmdDocs != null) return; cmdDocs = new StringBuffer("Available options:\n"); cmdMap = new HashMap (); Method[] methods = target.getClass().getMethods(); for (Method method: methods) { CommandArg argCmd = method.getAnnotation(CommandArg.class); if (argCmd != null) { cmdDocs.append(argCmd.value() + "\n " + argCmd.desc() + "\n"); cmdMap.put(argCmd.value().split(" ")[0], method); } } } private void showCommands(){ System.out.println(cmdDocs); } }
以上主要程式邏輯放在 exec 這個方法上,它會透過呼叫 initCommands 方法,建立所有選項常式之描述 (cmdDocs) 及對應 (cmdMap)。完成後,就是實際去解析命令列參數。如果沒有任何參數,就透過 showCommands 顯示說明。如果有,就進行選項與主程式方法的對應。
沒有留言:
張貼留言