本文是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语句结尾。