• Javassist中的一些特殊参数示例讲解


    官方文档中看到有几个比较特殊的标识符,还有几个比较特殊的标识符需要了解。
    标识符
    作用
    $0、$1、$2、 3 、 3、 3、…
    this和方法参数(1-N是方法参数的顺序)
    $args
    方法参数数组,类型为Object[]
    $$
    所有方法参数,例如:m($$)相当于m($1,$2,…)
    $cflow(…)
    control flow 变量
    $r
    返回结果的类型,在强制转换表达式中使用。
    $w
    包装器类型,在强制转换表达式中使用。
    $_
    返回的结果值
    $sig
    类型为java.lang.Class的参数类型对象数组
    $type
    类型为java.lang.Class的返回值类型
    $class
    类型为java.lang.Class的正在修改的类
    注意:
    a)不能引用在方法中其它地方定义的局部变量
    0
    改造一下,中间的代码块抽成一个方法调用变成一行,如下:
    public String pingString(int length) {
        long begin = System.nanoTime();
        String result = pingString$agent(length);
        long end = System.nanoTime();
        System.out.println(end - begin);
        return result;
    }
    
    public String pingString$agent(int length) {
        String result = "";
        for (int i = 0; i < length; i++) {
            result += (char) (i % 26 + 'a');
        }
        return result;
    }
    
    public static void main(String[] args) {
        StringService ss = new StringService();
        ss.pingString(10);
    }
    用javassist如何实现呢?
    package com.dxz;
    
    import com.dxz.service.StringService;
    import javassist.*;
    
    public class StatRuntime {
        public static void main(String[] args) throws NotFoundException, CannotCompileException {
            ClassPool pool = new ClassPool(true);
            String targClassName = "com.dxz.service.StringService";
            CtClass ctClass = pool.get(targClassName);
            CtMethod pingString = ctClass.getDeclaredMethod("pingString");
            CtMethod agentMethod = CtNewMethod.copy(pingString, ctClass, null);
            // 修改copy方法名称
            String newName = pingString.getName() + "$agent";
            agentMethod.setName(newName);
            //新方法加入到class
            ctClass.addMethod(agentMethod);
    
            //在javassist的环境里,这是在同一个代码块,前面定义的变量就可以访问了
            String insertCode = "{" +
                    "long begin = System.nanoTime();" +
                    "Object result=" + pingString.getName() + "$agent($$);" +
                    "long end = System.nanoTime();" +
                    "System.out.println(end - begin);" +
                    "return ($r)result;" +
                    "}";
            pingString.setBody(insertCode);
            //修改后的class载入到当前的classloader Thread.currentThread().getContextClassLoader();
            ctClass.toClass();
    
            StringService ss = new StringService();
            ss.pingString(10);
        }
    }
    结果:
    0
    b)不会对类型进行强制检查:例如:int start = System.currentTimeMillis();
    c)使用特殊的项目语法符号
    d)web插庄问题
    下面咱们来一起分别来看一下,分析一下,怎么用,有什么用吧。
    只介绍几个常见和常用到的吧。 有兴趣的话,剩下的可以看官方文档:http://www.javassist.org/tutorial/tutorial2.html
     
    (一)$0,$1,$2,…
    这个其实咱们已经在上面用到过了,再来细说一下吧。$0代表this,$1, 2 , 2, 2,…,依次对应方法中参数的顺序。 如果有:
    public void test(int a,int b,int c){}
    那么如果想引用a和b和c的话,需要这样:
    ctMethod.setBody(
       "return $1 + $2 + $3;"
    );
    对了还有:静态方法是没有$0的,所以静态方法下$0是不可用的。
     
    (二)$args
    $args变量表示所有参数的数组,它是一个Object类型的数组(new Object[]{…}),如果参数中有原始类型的参数,会被转换成对应的包装类型。比如原始数据类型为int,则会被转换成java.lang.Integer,然后存储在args中。
    例如我们测试代码如下:
    CtMethod ctMethod = new CtMethod(CtClass.voidType, "hello1", new CtClass[]{CtClass.intType, CtClass.doubleType}, ctClass);
    ctMethod.setModifiers(Modifier.PUBLIC);
    ctMethod.setBody("System.out.println($args);");
    编译后的结果如下:
    public void hello1(int var1, double var2) {
        System.out.println(new Object[]{new Integer(var1), new Double(var2)});
    }
    (三)$$
    变量 是 所 有 参 数 的 缩 写 , 参 数 用 逗 号 分 割 , 例 如 : m ( 是所有参数的缩写,参数用逗号分割,例如:m( 是所有参数的缩写,参数用逗号分割,例如:m()相当于:m($1,$2,$3,…)。
    如何使用呢?
    我们经常做一些代码优化,把较为复杂的方法内部的逻辑,提炼出来,提炼到一个公共的方法里。
    或者说一个方法调用另一个方法,这两个方法传递的参数是一样的时候就用到了。
    例如原java:
    public Object m1(String name,String age){
        ...省略10000行代码逻辑
    }
    提炼后的:
    提炼出来的代码,我们也可以在其他地方使用(所谓的公共的代码)。
    public Object m1(String name,String age){
        ...省略20行代码逻辑
        return m2(name,age);
    }
     
    private Object m2(String name,String age){
        ...
    }
    那么我们就造一个这个场景来说明一下用法吧。
    简单点来说,就是一个方法调用另一个方法,传递全参数时。
    CtMethod hello2 = new CtMethod(CtClass.voidType, "hello2", new CtClass[]{CtClass.intType, CtClass.doubleType}, ctClass);
    hello2.setModifiers(Modifier.PUBLIC);
    hello2.setBody("this.value = $1 + $2;");
    ctClass.addMethod(hello2);
     
    //添加一个hello1的方法
    CtMethod hello1 = new CtMethod(CtClass.voidType, "hello1", new CtClass[]{CtClass.intType, CtClass.doubleType}, ctClass);
    hello1.setModifiers(Modifier.PUBLIC);
    hello1.setBody("this.value = $1 + $2;");
    hello1.insertAfter("hello2($$);");
    编译后的结果:
    public void hello2(int var1, double var2) {
        this.value = (int)((double)var1 + var2);
    }
     
    public void hello1(int var1, double var2) {
        this.value = (int)((double)var1 + var2);
        Object var5 = null;
        this.hello2(var1, var2);
    }
    (四)$cflow(…)
    $cflow 的全名为:control flow,这是一个只读变量,返回指定方法递归调用的深度。
    我们以计算n的斐波拉契数列为例,来演示一下如何使用。
    我们正确的递归算法代码如下:
    public int f(int n){
        if(n <= 1){
            return n;
        }
        return f(n-1) + f(n - 2)
     }
    对于上面这段代码,我们可以这样写:
    CtMethod f = new CtMethod(CtClass.intType,"f", new CtClass[]{CtClass.intType}, ctClass);
    f.setBody("{if($1 <= 1){" +
              "     return $1;" +
              "}" +
              "return f($1 - 1) + f( $1 - 2);}");
    f.setModifiers(Modifier.PUBLIC);
    编译后的:
    public int f(int var1) {
        return var1 <= 1 ? var1 : this.f(var1 - 1) + this.f(var1 - 2);
    }
    那么我们只想在递归到前n次的时候打印log,我们该怎么做呢。
    例如,我们下面写的是"$cflow(f) == 1"时
    CtMethod f = new CtMethod(CtClass.intType,"f", new CtClass[]{CtClass.intType}, ctClass);
    f.setBody("{if($1 <= 1){" +
              "     return $1;" +
              "}" +
              "return f($1 - 1) + f( $1 - 2);}");
    f.setModifiers(Modifier.PUBLIC);
     
    //在代码body的前面插入
    f.useCflow("f");
    f.insertBefore("if($cflow(f) == 1){" +
                   "               System.out.println(\"我执行了,此时的n是:\"+$1);" +
                   "           }");
    编译后的代码:
    //
    // Source code recreated from a .class file by IntelliJ IDEA
    // (powered by FernFlower decompiler)
    //
     
    package com.ssdmbbl.javassist;
     
    import javassist.runtime.Cflow;
     
    public class Hello {
        public static Cflow _cflow$0 = new Cflow();
     
        public int f(int var1) {
            if (_cflow$0.value() == 1) {
                System.out.println("我执行到来第2次,此时的n是:" + var1);
            }
     
            boolean var6 = false;
     
            int var10000;
            try {
                var6 = true;
                _cflow$0.enter();
                if (var1 <= 1) {
                    var10000 = var1;
                    var6 = false;
                } else {
                    var10000 = this.f(var1 - 1) + this.f(var1 - 2);
                    var6 = false;
                }
            } finally {
                if (var6) {
                    boolean var3 = false;
                    _cflow$0.exit();
                }
            }
     
            int var8 = var10000;
            _cflow$0.exit();
            return var8;
        }
     
        public Hello() {
        }
    }
    通过查看源码,发先关键的一个地方是调用了Cflow对象的enter方法:
    public static Cflow _cflow$0 = new Cflow(); ... _cflow$0.enter();
    点进enter()的内部发现,调用了get().inc()方法:
    public class Cflow extends ThreadLocal<Cflow.Depth> {
        protected static class Depth {
            private int depth;
            Depth() { depth = 0; }
            int value() { return depth; }
            void inc() { ++depth; }
            void dec() { --depth; }
        }
     
        @Override
        protected synchronized Depth initialValue() {
            return new Depth();
        }
     
        /**
         * Increments the counter.
         */
        public void enter() { get().inc(); }
     
        /**
         * Decrements the counter.
         */
        public void exit() { get().dec(); }
     
        /**
         * Returns the value of the counter.
         */
        public int value() { return get().value(); }
    }
    而inc()方法控制着一个全局变量的增加操作。
    void inc() { ++depth; }
    boolean var6 = false;相当于是一个开关,控制着是否开始和结束。
    当参数var1<=1时,即结束
    if (var1 <= 1) { var10000 = var1; var6 = false; }
    我们可以使用反射来验证一下,测试代码如下:
    package com.ssdmbbl.javassist;
     
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
     
    /**
     * @author zhenghui
     * @description:
     * @date 2021/4/8 10:20 上午
     */
    public class Test {
     
        public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
     
            Class<?> aClass = Class.forName("com.ssdmbbl.javassist.Hello");
     
            //初始化这个类
            Object obj = aClass.newInstance();
     
            //获取所有的方法
            Method[] methods = aClass.getMethods();
            //遍历所有的方法
            for (Method method : methods) {
                //当方法为f的时候,进行调用
                if(method.getName().equals("f")){
                    //调用并传递参数为5,即f(5)
                    method.invoke(obj,5);
                }
            }
        }
    }
    执行的结果:
    为什么是2次呢?原因上面也说了:if(var1 <= 1){…},所以是
    我执行了,此时的n是:4 我执行了,此时的n是:3
    相关链接
    1、javassist的API接口文档
    2、javassist的github开源地址
    3、javassist的官网
    4、javassist官方的英文教程
  • 相关阅读:
    codeforces 169 div2 C
    poj 1062(最短路)
    sgu 118
    sgu 101
    poj 2446二分图匹配
    ural 1129 (求数据)
    C#中抽象类和接口的区别(转)
    在.net(C# or vb.net)中,Appplication.Exit 还是 Form.Close有什么不同?
    一道爱出的题目,就是前面两个数相加 用递归方法实现
    C#冒泡排序
  • 原文地址:https://www.cnblogs.com/duanxz/p/15664172.html
Copyright © 2020-2023  润新知