美文网首页程序员我爱编程
Javassist之内省与定制(二)

Javassist之内省与定制(二)

作者: bdqfork | 来源:发表于2018-06-21 23:16 被阅读0次

    本文是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之内省与定制(二)

        本文链接:https://www.haomeiwen.com/subject/velhyftx.html