V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
shellquery
V2EX  ›  程序员

知道 Python 语言的 Google Fire 项目么,我将它移植到了 Java 上

  •  
  •   shellquery · 2018-06-11 09:52:59 +08:00 · 1843 次点击
    这是一个创建于 2339 天前的主题,其中的信息可能已经有所发展或是发生改变。

    最近尝试了 Python 语言的开源命令行便捷工具库 Google Fire,它是用来加速用户编写命令行程序的一个小工具库,该工具使用非常方便,节省了编写命令行程序繁琐的参数解析代码的时间。

    巧用 Google Fire 简化 Python 命令行程序

    但是我发现 Java 语言还缺少这样一个工具库,于是花了 2 天时间,将 Google Fire 移植到了 Java 语言上,命名为 Startup。有了 Startup,Java 同学也可以从此不用解析繁琐的命令行参数了。

    Startup 项目的 Github 代码地址

    安装依赖

    <dependency>
    	<groupId>com.github.pyloque</groupId>
    	<artifactId>startup</artifactId>
    	<version>0.0.1</version>
    </dependency>
    

    让 Jedis 秒变命令行

    import java.io.IOException;
    
    import codehole.startup.Startup;
    import redis.clients.jedis.Jedis;
    
    public class Demo {
    
    	public static void main(String[] args) {
    		Startup.shell(new Jedis());
    	}
    
    }
    

    使用 maven 将程序打包成 jar 包文件 demo.jar ,接下来就可以体验命令行版本的 Jedis 了。

    $ java -jar demo.jar
    > @set codehole superhero
    OK
    > @get codehole
    superhero
    > @keys *
    [codehole]
    > @del codehole
    1
    > @sadd books str[]:java,golang,python
    1
    > @sadd books str[]:java,golang,python
    3
    > @smembers books
    [python, golang, java]
    > @scard books
    3
    

    我们还可以使用单行命令模式,将 shell 方法改成 fire 即可,有了 fire 函数,繁琐的命令行参数解析过程就可以彻底拜拜了。

    Startup.fire(new Jedis(), args);  // 需要传递 args 参数
    

    接下来,我们尝试一下单行命令的效果

    $ java -jar demo.jar @set codehole superhero
    OK
    $ java -jar demo.jar @get codehole
    superhero
    $ java -jar demo.jar @keys "*"
    [codehole]
    $ java -jar demo.jar @del codehole
    1
    $ java -jar demo.jar @sadd books str[]:java,golang,python
    3
    $ java -jar demo.jar @smembers books
    [python, golang, java]
    $ java -jar demo.jar @scard books
    3
    

    Redis 的 set 命令在 Jedis 里存在多个重载函数,很难记清楚具体有哪些参数。不过没关系,Startup 提供了类自省功能,可以列出指定匹配模式的函数调用形式。

    $ java -jar demo.jar ! @set
    class: redis.clients.jedis.Jedis
    	method: set(String arg0, String arg1, String arg2)
    	method: set(String arg0, String arg1, String arg2, String arg3, int arg4)
    	method: set(String arg0, String arg1)
    	method: set(String arg0, String arg1, String arg2, String arg3, long arg4)
    class: redis.clients.jedis.BinaryJedis
    	method: set(byte[] arg0, byte[] arg1, byte[] arg2)
    	method: set(byte[] arg0, byte[] arg1, byte[] arg2, byte[] arg3, int arg4)
    	method: set(byte[] arg0, byte[] arg1, byte[] arg2, byte[] arg3, long arg4)
    	method: set(byte[] arg0, byte[] arg1)
    

    自省操作符是!,它连带祖先类一会自省了。当 set 方法有如此多时,如何调用具体哪个 set 方法呢? Startup 使用参数数量和参数类型来匹配函数,如果有多个函数满足匹配条件,就使用第一个函数。比如现在要调用 byte[]类型的 set 函数,我们可以这样写

    > java -jar demo.jar @set byte[]:1,2,3 byte[]:10,20
    OK
    > java -jar demo.jar @get byte[]:1,2,3
    [B@ae45eb6
    

    因为输出是一个 byte[]数组,所以显示的是它的 toString()调用的结果。如果用户不满意输出效果,可以通过自定义IConsole.print方法来优化输出。

    Startup.fire(Object target, String[] args, IConsole console);
    

    细节规则

    1. 方法调用使用 @前缀,如 @set
    2. 字段访问使用$前缀,如$host
    3. 方法调用如果遇到同样参数数量的方法不止一个,那么在传递参数时就必须携带参数类型前缀信息,便于 startup 确定具体应该调用哪个方法。如@set str:codehole str:superhero,表示调用两个参数都是 String 类型的 set 方法。如果不给予参数类型提示,那么自动使用第一个找到的方法,这可能不是你想要的。
    4. startup 的 target 参数可以是普通对象,也可以是类。比如Startup.fire(Jedis.class, args),那么后续的参数作用对象就是 Jedis.class。
    5. 如果 target 是类,那么可以使用操作符+号对类进行实例化,后面跟着构造器的参数列表。比如 Jedis 有构造器Jedis(String host, int port),那么就可以这样实例化+ str:localhost int:6379
    6. 如果 target 是类,并且有默认构造器的话,那么如果要访问实例方法或者实例字段的话,就会对这个类进行自动实例化。如果没有默认构造器,这时候就会抛出异常。
    7. 如果某个方法返回值为 void,Startup 会进行特殊处理,返回调用对象自身。
    8. 自省操作符!可以使用! @显示所有方法,可以使用! $显示所有字段,如果不带参数,等价于! *列出所有的字段和方法。像 Jedis 类里面实例方法特别多,如果全部列出来会让人崩溃,这时可以用第三个参数 maxlen,表示最多显示多少行。比如! @ 100最多显示 100 行方法。还可以通过模式匹配*号来查找方法,比如! @*set*匹配所有包行 set 字符串的方法列表。
    9. 输入 exit 和 q 可以退出命令行 shell。

    实例演示

    下面我们使用链式调用的例子来演示 Startup 的强大威力。首先我们编写一个 Counter 类,可以对该类里面的整形字段 value 进行 incr 和 decr 操作。然后使用 Startup 对它进行 shell 化。同时我们定一个 coeff 静态变量,用于放大自增自减的效果。

    import codehole.startup.Startup;
    
    public class Demo {
    
    	static class Counter {
    		int value;
    		static int coeff = 1;
    
    		public Counter() {
    			this(10);
    		}
    
    		public Counter(int value) {
    			this.value = value;
    		}
    
    		public static int coeff() {
    			return coeff;
    		}
    		
    		public static void coeff(int c) {
    			coeff = c;
    		}
    
    		public Counter incr() {
    			return incr(1);
    		}
    
    		public Counter incr(int v) {
    			this.value += v * coeff;
    			return this;
    		}
    
    		public Counter decr() {
    			return incr(1);
    		}
    
    		public Counter decr(int v) {
    			this.value -= v * coeff;
    			return this;
    		}
    
    		public int value() {
    			return value;
    		}
    		
    		public String toString() {
    			return String.format("Counter(value=%d)", value);
    		}
    
    	}
    
    	public static void main(String[] args) {
    		Startup.shell(Counter.class); // 注意,这里的目标是类
    	}
    
    }
    

    我们运行一下,体验一下 Startup 的神奇魅力。

    自省

    $ java -jar demo.jar
    # 列出所有方法
    > ! @
    class: codehole.startup.demo.Demo.Counter
    	method: value()
    	method: decr()
    	method: decr(int arg0)
    	method: static coeff(int arg0)
    	method: static coeff()
    	method: incr(int arg0)
    	method: incr()
    # 列出所有字段
    > ! $
    class: codehole.startup.demo.Demo.Counter
    	field: int value
    	field: static int coeff
    # 列出所有的 incr 方法
    > ! @incr
    class: codehole.startup.demo.Demo.Counter
    	method: incr(int arg0)
    	method: incr()
    # 列出所有的 decr 方法
    > ! @decr
    class: codehole.startup.demo.Demo.Counter
    	method: decr()
    	method: decr(int arg0)
    # 列出后缀为 cr 的方法
    > ! @*cr
    class: codehole.startup.demo.Demo.Counter
    	method: decr()
    	method: decr(int arg0)
    	method: incr(int arg0)
    	method: incr()
    

    构造器

    # 默认构造器
    > +
    Counter(value=10)
    # 调用构造器 Counter(int)
    > + 5
    Counter(value=5)
    # 构造完继续获取实例字段
    > + $value
    10
    > + 5 $value
    5
    # 构造完继续调用实例方法
    > + @value
    10
    > + 5 @value
    5
    # 对类[访问实例字段或调用实例方法]会自动构造该类的默认实例,再回调方法或者访问实例字段
    > $value
    10
    > @value
    10
    

    静态字段和方法

    # 对类调用静态方法和对实例调用静态方法效果是一样的
    > @coeff
    1
    > + @coeff
    1
    # 对类访问静态字段和对实例访问静态字段效果是一样的
    > $coeff
    1
    > + $coeff
    1
    # 返回类型为 void 的方法会自动调整返回结果为调用对象
    > @coeff 2
    class codehole.startup.demo.Demo$Counter
    > + @coeff 2
    Counter(value=10)
    > @coeff 2 $coeff
    2
    

    链式调用

    > @incr @incr @incr 5 @incr 5 $value
    22
    > @incr 5 @decr 5 @incr 5 @decr 5 $value
    10
    > + @incr @incr @incr 5 @incr 5 $value
    22
    > + @incr 5 @decr 5 @incr 5 @decr 5 $value
    10
    

    总结

    Java 版本的 Startup 和 Google Fire 使用上还是有一些不一样,这个是语言的机制差别所致。但是使用上是大同小异,基本上 Google Fire 能做到的事,Startup 都能做了,只是在使用形式上略有不同。

    Startup 继承了 Fire 的优良传统,小巧轻便没有任何依赖项,使用一行代码就把命令行的繁琐解析过程给彻底消灭了。同时还提供了 Fire 所欠缺的自省功能和交互式 shell,用户使用 startup 进行调试工作时会无比轻松。

    阅读更多高级文章,关注微信订阅号「码洞」

    tysx
        1
    tysx  
       2018-06-11 12:47:46 +08:00
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3361 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 30ms · UTC 10:48 · PVG 18:48 · LAX 02:48 · JFK 05:48
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.