Javassist之内省与定制(二)

本文是Javassist之内省与定制(一)的续文,需要看完上篇文章之后才能阅读,请先阅读上个章节。

$args

变量 $args 是表示所有参数的数组。是一个Object类型的数组。如果参数的类型是如int一样的基本类型,参数值将会自动转换为包装类。因此, $args[0] 等同于$1,除非第一个参数是基本类型。注意,$args[0]不等同于$0$0表示this。

如果一个Object数组赋值给$args,那么每一个数组的值都会被传入到每一个参数。如果参数的类型是基本类型,对应的元素类型必须是包装类。在给参数赋值前 ,包装类的值将会被转换为基本类型的值。

$$

变量$$是所有参数以逗号隔开的列表缩写。例如,如果move()有三个参数,那么

move($$)

等价于:

move($1,$2,$3)

如果move()没有参数,move($$)等价于move()。

$$还有另一种使用方式。如果你写出如下表达式:

exMove($$, context)

这个表达式等价于:

exMove($1, $2, $3, context)

注意,$$使方法调用的泛型符号相对于参数个数。它常常与下面的$proceed一起使用。

$cflow

$cflow意思是“控制流”。这个只读变量返回指定方法递归调用的深度。

假设下面的方法被一个CtMethod对象cm表示:

int fact(int n) {
    if (n <= 1)
        return n;
    else
        return n * fact(n - 1);
}

为了$cflow,首先声明$cflow被用来监听方法fact()的调用:

CtMethod cm = ...;
cm.useCflow("fact");

useCflow()的参数是声明$cflow变量的标识。任何一个合法的Java名称都可以用来作为标识。因此标识符可以包含.(点),例如,“my.Test.fact”也是一个合法的标识符。

$cflow(fact)表示了cm指定的方法的递归调用深度。当方法第一次被调用时,$cflow(fact)的值为0,而在内部递归调用时,值为1.例如,

cm.insertBefore("if ($cflow(fact) == 0)"
              + "    System.out.println(\"fact \" + $1);");

输出方法fact()以便展示参数。因为$cflow(fact)被检测,如果fact()在内部递归调用,就不显示参数。

$cflow的值是当前线程的当前最顶层栈帧下,cm指定方法关联的栈帧数量。$cflow也可以在cm指定的方法之外的其他方法中访问。

$r

$r表示方法的返回类型。它只能被用在强制转换表达式中。例如,这是一个典型用法:

Object result = ... ;
$_ = ($r)result;

如果返回类型是基本类型,那么($r)遵循特殊的机制。首先,如果操作数类型在强制转换表示中是基本类型,($r)正常转换到返回类型。而另一方面,如果操作数是包装类,($r)将包装类转换到指定的返回类型。例如,如果返回类型是int,那么($r)从java.lang.Integer转换成int。

如果返回类型是void,那么($r)不会进行转换;它什么也不会做。然而,如果操作数是一个void方法调用,($r)返回null。例如,如果返回类型是void且foo()是一个void方法,那么

$_ = ($r)foo();

是一个合法的语句。

转换操作符($r)也用在return语句中。即使返回类型是void,下面的return语句也是正确的:

return ($r)result;

在这里,result是一个本地变量。由于指定了($r),所以返回值被释放了。这个return语句与没有返回值的return语句相同:

return;

$w

$w表示一个包装类。它必须被用在强制转换表达式中。($w)将基本数据类型转换成对应的包装类。

下面是一个示例:

Integer i = ($w)5;

选定的包装类取决于表达式中($w)后数据的类型。如果表达式的类型是double,那么包装类的类型是java.lang.Double。

如果跟随($w)的表达式类型不是一个基本类型,($w)什么也不会做。

$_

CtMethod和CtConstructor的insertAfter()插入编译后的代码到方法的末尾。在传给insertAfter()的语句中,不但可以是上面介绍的$0$1等,也可以是$_

变量$_表示方法的返回值。变量的类型就是方法的返回类型。如果返回类型是void,$_的类型就是Object,值为null。

尽管insertAfter()插入的编译后的代码是在方法正常控制流程之前返回的,但它也可以在异常从方法中抛出时执行。想要在异常抛出时执行,insertAfter()的第二个参数asFinally必须为true。

如果异常被抛出,insertAfter()插入的编译后的代码被当作finally执行。在编译后代码中$_的值为0或者null。编译后的代码执行完后,初始的异常重新抛出给调用者。注意,$_的值不再返回给调用者;它将会被释放。

$sig

$sig的值为一个java.lang.Class的对象数组,它表示与声明顺序相同的参数正式类型。

$type

$type的值为java.lang.Class的对象数组,它表示返回值的正式类型。在构造方法中,这个变量指向Void.class。

$class

$class的值为java.lang.Class对象,它表示声明的修改的方法的类。表示$0的类型。

addCatch()

addCatch()插入代码片段到方法体中,使其在方法体抛出异常之后控制返回给调用者时执行。在插入的代码片段的源代码文本中,异常的值由特殊的变量$e指代。

例如,这个程序:

CtMethod m = ...;
CtClass etype = ClassPool.getDefault().get("java.io.IOException");
m.addCatch("{ System.out.println($e); throw $e; }", etype);

将m表示的方法体表示成如下:

try {
    the original method body
}
catch (java.io.IOException e) {
    System.out.println(e);
    throw e;
}

注意,插入的代码片段必须以throw或者return语句结尾。

你可能感兴趣的:(Javassist之内省与定制(二))