Javassist中的一些特殊参数示例讲解
在官方文档中看到有几个比较特殊的标识符,还有几个比较特殊的标识符需要了解。
注意:
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官方的英文教程