一 前言
Javassist (Java Programming Assistant) makes Java bytecode manipulation simple. It is a class library for editing bytecodes in Java; it enables Java programs to define a new class at runtime and to modify a class file when the JVM loads it. Unlike other similar bytecode editors, Javassist provides two levels of API: source level and bytecode level. If the users use the source-level API, they can edit a class file without knowledge of the specifications of the Java bytecode. The whole API is designed with only the vocabulary of the Java language. You can even specify inserted bytecode in the form of source text; Javassist compiles it on the fly. On the other hand, the bytecode-level API allows the users to directly edit a class file as other editors.
Javassist 提供了java类库,用于方便操控Java字节码。功能包括:运行时创建java class,修改class。与其他同类工具(asm等)不同的是,Javassist提供了两个层面的API:
1.java代码层
2.字节码层
通过java代码层,开发者即时对字节码不是很熟悉,也可以非常方便快速的完成字节码的修改。
二 示例
定义一个Bean类,代码如下
public class Bean {
public static void show(String text) {
System.out.print("hey man " + text);
}
public static void main(String[] argv) {
show("add timing ");
}
}
直接运行,会打印
hey man add timing
如果想要统计show方法的执行时间,我们可以在写代码的时候直接在show方法体开始和结束的地方加上计时的代码,这样很简单。但是假如我们期望动态修改show方法,直接修改Test.class类字节码,那么可以通过Javassist来完成。具体方法如下:
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.CtNewMethod;
import javassist.NotFoundException;
public class JassistLearn {
public static void main(String[] argv) {
try {
CtClass clas = ClassPool.getDefault().get("Bean");
addTiming(clas,"show");
} catch (NotFoundException e) {
e.printStackTrace();
} catch (CannotCompileException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
private static void addTiming(CtClass clas, String mname)
throws NotFoundException, CannotCompileException, IOException {
CtMethod mold = clas.getDeclaredMethod(mname);
String nname = mname+"Impl";
mold.setName(nname);
CtMethod mnew = CtNewMethod.copy(mold, mname, clas, null);
String type = mold.getReturnType().getName();
StringBuffer body = new StringBuffer();
body.append("{\nlong start = System.currentTimeMillis();\n");
if (!"void".equals(type)) {
body.append(type + " result = ");
}
body.append(nname + "($$);\n");
body.append("System.out.println(\"Call to method " + mname +
" took \" +\n (System.currentTimeMillis()-start) + " +
"\" ms.\");\n");
if (!"void".equals(type)) {
body.append("return result;\n");
}
body.append("}");
mnew.setBody(body.toString());
clas.addMethod(mnew);
clas.writeFile("D:/eclipse/server/JavaSistLearn/bin");
}
}
代码注释包含了每一步对应的意义,执行过程总结一下主要是:
1. 通过ClassPool找到Bean.class类文件
2. 修改show方法名称为一个临时名称showImpl
3. 拷贝一个新方法,方法名是show,方法体与showImpl方法的内容相同
4. 修改新方法的方法体,包含三部分:方法开始添加start变量标记当前时刻;执行showImpl;计算当前时间与start的差值,算出showImpl的执行耗时
5. 将新方法添加到Bean.calss文件中;同步更新Bean.class文件到磁盘
通过上面五个步骤就完成了Bean.class字节码的修改
执行JassistLearn,打印数据如下:
hey man add timing Call to method show took 0 ms.
通过JD-DUI工具查看Bean class文件,代码如下:
import java.io.PrintStream;
public class Bean
{
public static void showImpl(String text)
{
System.jdField_out_of_type_JavaIoPrintStream.print("hey man " + text);
}
public static void main(String[] argv) {
show("add timing ");
}
public static void show(String paramString)
{
long l = System.currentTimeMillis();
showImpl(paramString);
System.jdField_out_of_type_JavaIoPrintStream.println("Call to method show took " + (System.currentTimeMillis() - l) + " ms.");
}
}
工程下载地址
三 总结
通过示例看到通过javassist api可以很简便的在运行时修改class字节码,并且不要求使用者对字节码本身非常熟悉。利用这个特性可以实现aop,安卓应用热修复等功能。不过最新github上的javassist.jar在使用过程会报关于StackWalker.StackFrame的错误,需要使用Jdk1.9。
四 参考
Javassist github
Javassist 官网
Javassist api参考
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)