Javassist学习总结1
1.摘要
javassist是一个"Java字节码"操作类库.
2.应用场景
1. 读取.class文件到内存,
新增或删除字段或方法,
修改字段或方法的 访问级别, 是否final, 是否static, 是否abstract
改换类名,
改换类继承的接口或父类
...
然后写回.class文件.
2. ClassLoader加载某个.class文件时, 动态的在方法的前后加点字节码, 实现动态代理及AOP的功能
3.API模型
javassist提供了, 如下两种级别的API
1.高级别API
无需"Java字节码"相关知识, 就可操作"Java字节码", 低性能, 易使用.
2.低级别API
需要"Java字节码"相关知识, 才可操作"Java字节码", 高性能, 难使用.
源码实现上, "高级别API" 依赖了 "低级别API"
4.代码示例
高级别API的简单实用
package demo.javassist;
import javassist.*;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
/**
* 深入理解JVM字节码 - [6.2]Javassist介绍
*
* 这本书, 主要介绍了, 高级别API的使用方式
*/
@DisplayName("深入理解JVM字节码 - [6.2]Javassist介绍")
public class Demo4Book {
@Test
@DisplayName("创建类")
public void testMakeClass() throws Throwable {
ClassPool cp = ClassPool.getDefault();
CtClass cc = cp.makeClass("demo.MyMain");
cc.writeFile();
}
@Test
@DisplayName("新增字段")
public void testAddField() throws Throwable {
ClassPool cp = ClassPool.getDefault();
cp.insertClassPath(".");
CtClass cc = cp.get("demo.MyMain");
CtField cf = new CtField(CtClass.intType, "i", cc);
cf.setModifiers(Modifier.PRIVATE);
cc.addField(cf);
cc.writeFile();
}
@Test
@DisplayName("新增方法")
public void testAddMethod() throws Throwable {
ClassPool cp = ClassPool.getDefault();
cp.insertClassPath(".");
CtClass cc = cp.get("demo.MyMain");
// 参数依次为 返回类型, 方法名称, 参数类型, 方法所属类
CtMethod cm = new CtMethod(CtClass.intType, "foo",
new CtClass[]{CtClass.intType, CtClass.intType}, cc);
// 设定方法的 修饰符(public, static等等), 具体参考Modifier
cm.setModifiers(Modifier.PUBLIC);
cc.addMethod(cm);
cc.writeFile();
}
@Test
@DisplayName("设定方法体")
public void testMethodSetBody() throws Throwable{
ClassPool cp = ClassPool.getDefault();
cp.insertClassPath(".");
CtClass cc = cp.get("demo.MyMain");
CtMethod cm = cc.getMethod("foo", "(II)I");
// $0为this, $1为第一个方法参数, $2位第二个方法参数
cm.setBody("{System.out.println(\"$0 = this \" + $0); return $1 + $2 + i; }");
cc.writeFile();
}
@Test
@DisplayName("方法体-前加代码")
public void testMethodInsertBefore() throws Throwable{
ClassPool cp = ClassPool.getDefault();
cp.insertClassPath(".");
CtClass cc = cp.get("demo.MyMain");
CtMethod cm = cc.getMethod("foo", "(II)I");
cm.insertBefore("System.out.println(\" method start2 \");");
cm.insertBefore("System.out.println(\" method start1 \");");
cc.writeFile();
}
@Test
@DisplayName("方法体-后加代码")
public void testMethodInsertAfter() throws Throwable{
ClassPool cp = ClassPool.getDefault();
cp.insertClassPath(".");
CtClass cc = cp.get("demo.MyMain");
CtMethod cm = cc.getMethod("foo", "(II)I");
cm.insertAfter("System.out.println(\" method end1 \");");
cm.insertAfter("System.out.println(\" method end2 \");");
cc.writeFile();
}
@Test
@DisplayName("$开头的特殊标识符列表")
public void test$Args() throws Throwable {
ClassPool cp = ClassPool.getDefault();
cp.insertClassPath(".");
CtClass cc = cp.get("demo.MyMain");
CtMethod cm = cc.getMethod("foo", "(II)I");
cm.setBody("{return 0;}");
// 方法参数数组 // new Object[]{new Integer(var1), new Integer(var2)}
cm.insertAfter("System.out.println(\" $args = \" + $args);");
// 返回值 // 0
cm.insertAfter("System.out.println(\" $_ \" + $_);");
// 参数类型数组 // Desc.getParams("(II)I") // 类型描述符
cm.insertAfter("System.out.println(\" $sig \" + $sig);");
// 返回类型 // Desc.getType("I")
cm.insertAfter("System.out.println(\" $type \" + $type);");
// 当前类型 // Desc.getClazz("demo.MyMain")
cm.insertAfter("System.out.println(\" $class \" + $class);");
// 所有参数 // this.foo(var1, var2);
cm.insertAfter("foo($$);");
// $r TODO
// $w TODO
// $cflow 递归调用深度
cc.writeFile();
}
@Test
@DisplayName("添加一个fibonacci方法")
public void testAddFibonacci() throws Throwable {
ClassPool cp = ClassPool.getDefault();
cp.insertClassPath(".");
CtClass cc = cp.get("demo.MyMain");
// 参数依次为 返回类型, 方法名称, 参数类型, 方法所属类
CtMethod cm = new CtMethod(CtClass.intType, "fibonacci",
new CtClass[]{CtClass.intType}, cc);
// 设定方法的 修饰符(public, static等等), 具体参考Modifier
cm.setModifiers(Modifier.PUBLIC);
cm.setBody("if ($1 <=1) {return $1;} " +
"else {return fibonacci($1-1) + fibonacci($1-2);}");
cc.addMethod(cm);
cc.writeFile();
}
@Test
@DisplayName("$cflow-递归调用深度")
public void test$cflow() throws Throwable {
ClassPool cp = ClassPool.getDefault();
cp.insertClassPath(".");
CtClass cc = cp.get("demo.MyMain");
// public static Cflow _cflow$0 = new Cflow();
// public class Cflow extends ThreadLocal<Cflow.Depth>
// 实现方式, 线程本地变量
CtMethod cm = cc.getMethod("fibonacci", "(I)I");
cm.useCflow("fibonacci");
cm.insertBefore(
"if ($cflow(fibonacci) == 0) {" +
"System.out.println(\" fibonacci init \" + $1);" +
"}"
);
cc.writeFile();
}
@Test
@DisplayName("除零异常")
public void testDivByZero() throws Throwable{
ClassPool cp = ClassPool.getDefault();
cp.insertClassPath(".");
CtClass cc = cp.get("demo.MyMain");
CtMethod cm = cc.getMethod("foo", "(II)I");
cm.setBody("{int a = 1; int b = 0; int c = a / b; return $1 + $2;}");
cc.writeFile();
}
@Test
@DisplayName("除零异常, insertAfter finally")
public void testInsertAfterFinally() throws Throwable {
ClassPool cp = ClassPool.getDefault();
cp.insertClassPath(".");
CtClass cc = cp.get("demo.MyMain");
CtMethod cm = cc.getMethod("foo", "(II)I");
cm.insertAfter("System.out.println(\" $_ \" + $_);", true);
cc.writeFile();
}
}
低级别API的简单实用
package demo.javassist;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.bytecode.AccessFlag;
import javassist.bytecode.ClassFile;
import javassist.bytecode.FieldInfo;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import java.io.*;
/**
* Javassist官方参考
* 1.官网: http://www.javassist.org/
* 2.入门指南: http://www.javassist.org/tutorial/tutorial.html
* 3.在线API文档: http://www.javassist.org/html/index.html
*/
@DisplayName("Javassist官方指南")
public class Demo4Tutorial {
/**
* 三种获取ClassFile的方式
* @throws Throwable
*/
@Test
@DisplayName("三种获取ClassFile的方式")
public void testThreeWayGetClassFile() throws Throwable {
// 方式1(高级别API): 从CtClass获取ClassFile
ClassPool cp = ClassPool.getDefault();
cp.insertClassPath(".");
CtClass cc = cp.get("demo.MyMain");
ClassFile cf = cc.getClassFile();
// 方式2(低级别API): 直接从数据流构建ClassFile
BufferedInputStream fin = new BufferedInputStream(new FileInputStream("./demo/MyMain.class"));
cf = new ClassFile(new DataInputStream(fin));
// 方式3(低级别API): 手动构建ClassFile
cf = new ClassFile(false, "demo.Foo", null);
cf.setInterfaces(new String[] { "java.lang.Cloneable" });
FieldInfo f = new FieldInfo(cf.getConstPool(), "width", "I");
f.setAccessFlags(AccessFlag.PUBLIC);
cf.addField(f);
cf.write(new DataOutputStream(new FileOutputStream("./demo/Foo.class")));
}
/**
* ClassFile读取和写入用到的核心方法
* 读取: ClassFile.read
* 写入: ClassFile.write
*
* 熟悉这两个方法, 对Java字节码结构的理解, 帮助会非常大!!!
*/
@Test
@DisplayName("ClassFile读取和写入")
public void testReadAndWriteClassFile() throws Throwable {
// ClassFile读取
BufferedInputStream fin = new BufferedInputStream(new FileInputStream("./demo/MyMain.class"));
ClassFile cf = new ClassFile(new DataInputStream(fin));
// ClassFile写入
cf.write(new DataOutputStream(new FileOutputStream("./demo/MyMainCopy.class")));
}
}
5.相关参考
一. Javassist官方参考
1.官网: http://www.javassist.org/
可以下载发布包, 里面有(Jar包, 源码, 使用样例, 入门指南).
2.入门指南: http://www.javassist.org/tutorial/tutorial.html
就三页, 写的较为全面, 但是比较琐碎, 容易陷入细节泥潭
建议先跑几个demo, 按照如上模型, 调试下源码 (源码比较简单), 然后再来看
3.在线API文档: http://www.javassist.org/html/index.html
JavaDoc文档
二. [书籍] 深入理解JVM字节码(张亚) [6.2]Javassist介绍
围绕字节码相关的知识, 介绍都较为全面
三. [书籍] 深入理解Java虚拟机(周志明)