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官方的英文教程


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM