在
官方文檔中看到有幾個比較特殊的標識符,還有幾個比較特殊的標識符需要了解。
注意:
a)不能引用在方法中其它地方定義的局部變量

改造一下,中間的代碼塊抽成一個方法調用變成一行,如下:
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); } }
結果:

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