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

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

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

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

Startup项目的Github代码地址 https://github.com/pyloque/startup

安装依赖

<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进行调试工作时会无比轻松。

原文发布于微信公众号 - 码洞(codehole)

原文发表时间:2018-06-11

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏奔跑的蛙牛技术博客

什么是字节码?

字节码(Byte-code)是一种包含执行程序,由一序列 op 代码/数据对组成的二进制文件,是一种中间码。字节是电脑里的数据量单位。

4763
来自专栏Spark学习技巧

JAVA之ClassLoader

JAVA基础系列之ClassLoader 一,Java类的加载、链接与初始化 1,加载:查找并加载类的二进制数据 • 通过一个类的全限定名来获取定义此类的二进制...

2219
来自专栏博岩Java大讲堂

多线程--同步与锁

2433
来自专栏xingoo, 一个梦想做发明家的程序员

堆 栈-相关知识

一、预备知识—程序的内存分配 一个由c/C++编译的程序占用的内存分为以下几个部分 1、栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量...

1978
来自专栏JMCui

多线程编程学习二(对象及变量的并发访问).

一、概念 非线程安全:会在多个线程对同一个对象中的实例变量进行并发访问时发生,产生的后果就是"脏读",也就是取到的数据其实是被更改过的. 线程安全:获得的实例变...

39814
来自专栏我的博客

PHP反射机制

PHP反射机制它是用来导出或提取出关于类、方法、属性(私有保护等属性也能获取)、参数等的详细信息,包括注释。 <?php class Test{ ...

4037
来自专栏java思维导图

深入了解Java之虚拟机内存

用来指示程序执行哪一条指令,这跟汇编语言的程序计数器的功能在逻辑上是一样的。JVM规范中规定,如果线程执行的是非native方法,则程序计数器中保存的是当前需要...

893
来自专栏我和PYTHON有个约会

16.pass关键字

在项目开发的过程中,某些情况下,我们定义了函数,但是对于函数中具体的代码临时没有确定,如:

1043
来自专栏待你如初见

Java多线程

1323
来自专栏爱撒谎的男孩

多线程的使用

1735

扫码关注云+社区

领取腾讯云代金券