1. 第一个简单的案例
第一个案例展示了给一个已存在的类
Student新增成员变量age1age2,新增方法getAge2setAge2,并且实例化对象后 调用新增的方法
@Test
public void test1() throws Exception {
// ClassPool是由装载了很多CtClass对象的HashTable组成
ClassPool pool = ClassPool.getDefault();
// 从class池中获取 Student类
// 如果尝试获取一个 不存在的类,会抛出 javassist.NotFoundException
CtClass ctClass = pool.get("com.maoxh.bytecode.javassist.other.Student");
/**
* 声明一个字段 age1 ,类型是 int
* 参数1:字段类型 , CtClass.intType,doubleType....等8种基本数据类型 + 一个 voidType, 如果是对象类型,请参考第二个field
* 参数2:字段名
* 参数3:声明的类
*/
CtField field1 = new CtField(CtClass.intType, "age1", ctClass);
// 设置静态的私有的。 Modifier的类型是2的幂次,修饰类型的时候直接类型相加
field1.setModifiers(Modifier.STATIC + Modifier.PRIVATE);
// 声明一个字段 age2 ,类型是 Integer
CtClass integerClass = pool.get(Integer.class.getName());
CtField field2 = new CtField(integerClass, "age2", ctClass);
field2.setModifiers(Modifier.PRIVATE);
// 添加
ctClass.addField(field1);
ctClass.addField(field2);
/**
* 创建一个方法
* 参数1:方法返回类型
* 参数2:方法名
* 参数3:参数列表
* 参数4:声明的类
*/
CtMethod getAgeMethod = new CtMethod(integerClass, "getAge2", new CtClass[]{}, ctClass);
getAgeMethod.setModifiers(Modifier.PUBLIC);
getAgeMethod.setBody("{ return age2;}");
ctClass.addMethod(getAgeMethod);
// 这里需要注意,字段 age2 是包装类Integer,所以在创建setAge2方法的时候,参数列表也必须是包装类,如果是int,则会出错
CtMethod setAgeMethod = new CtMethod(CtClass.voidType, "setAge2", new CtClass[]{integerClass}, ctClass);
setAgeMethod.setModifiers(Modifier.PUBLIC);
/**
* $1 = 第一个参数,也就是 Integer。 $2就是第二个参数,以此类推。$0=this
*/
setAgeMethod.setBody("{ $0.age2 = $1;}");
ctClass.addMethod(setAgeMethod);
// 可以写入文件来查看生成的 CtClass
// ctClass.writeFile("your path");
Class<?> aClass = ctClass.toClass();
Object o = aClass.newInstance();
Method setAge2 = aClass.getDeclaredMethod("setAge2", Integer.class);
setAge2.invoke(o, 20);
Method getAge2 = aClass.getDeclaredMethod("getAge2");
Object invoke = getAge2.invoke(o);
System.out.println("age2=" + invoke);
}
2. ClassPool
ClassPool是一个类池,主要由装载了很多CtClass对象的HashTable组成。操作字节码需要从ClassPool中获取CtClass对象.对象的装载是根据搜索路径来的。默认与jvm的搜索路径一致。如果你的类在默认路径找不到,你也可以自定义新增搜索路径,下面展示了几个方法来新增搜索路径。
同时javassist允许你自己创建ClassPool。下面的案例创建了一个 使用默认搜索路径的pool,以及创建了一个带有父pool的子ClassPool
@Test
public void classPool_test() throws Exception {
// ClassPool.getDefault()方法的搜索路径和JVM的搜索路径是一致的。
// 如果程序运行在JBoss或者Tomcat服务器上,
// 那么ClassPool对象也许不能够找到用户类,原因是应用服务器用的是多个class loader
ClassPool pool = ClassPool.getDefault();
// 新增一个 /usr/local/javalib 作为搜索目录
pool.insertClassPath("/usr/local/javalib");
// 新增一个当前类的路径下面的类对象,也可以将 this.getClass替换为其他类
pool.insertClassPath(new ClassClassPath(this.getClass()));
// 也可以通过url 和 字节码
// http://www.javassist.org/tutorial/tutorial.html
// 此外还可以自己创建classPool,参数 true=使用默认path
ClassPool mypool = new ClassPool(true);
mypool.appendClassPath(new ClassClassPath(this.getClass()));
// 此外还可以自己创建 一个及联的classPool,参数 pool=父pool
ClassPool child = new ClassPool(pool);
child.insertClassPath("./classes");
// child的get,会先尝试从 父pool获取,如果获取不到,就从自己的path中获取
CtClass ctClass = child.get("com.maoxh.bytecode.javassist.other.Student");
// 也可以改变搜索行为,从子类先获取,然后查找父pool
child.childFirstLookup = true;
}
3. CtField
3.1 通过构造器创建
以下的案例,展示操作新增Field,
CtField field1 = new CtField(CtClass.intType, "age1", ctClass);
- 参数1:类型 , CtClass.intType,doubleType....等8种基本数据类型 + 一个 voidType
- 参数2:字段名
- 参数3:声明的类
字段描述符javassist.Modifier的值是2的幂次,在设置描述符的时候,直接相加。
public static final int PUBLIC = 1;
public static final int PRIVATE = 2;
public static final int PROTECTED = 4;
public static final int STATIC = 8;
public static final int FINAL = 16;
public static final int SYNCHRONIZED = 32;
public static final int VOLATILE = 64;
public static final int VARARGS = 128;
public static final int TRANSIENT = 128;
public static final int NATIVE = 256;
public static final int INTERFACE = 512;
public static final int ABSTRACT = 1024;
public static final int STRICT = 2048;
public static final int ANNOTATION = 8192;
public static final int ENUM = 16384;
以下的例子将新增一个 private static final int age1; 的成员变量
ClassPool pool = ClassPool.getDefault();
CtClass ctClass = pool.get("com.maoxh.bytecode.javassist.other.Student");
CtField field1 = new CtField(CtClass.intType, "age1", ctClass);
field1.setModifiers(Modifier.STATIC + Modifier.FINAL + Modifier.PRIVATE);
ctClass.addField(field1);
如果成员变量类型不是基本数据类型呢?通过ClassPool.get(className)来获取类型
CtClass listCtClass = pool.get(List.class.getName());
CtField field2 = new CtField(listCtClass, "myList", ctClass);
ctClass.addField(field2);
3.2 通过CtField.make(src,CtClass)来创建
java.lang包下面的类无需导入,其他的类需要
pool.importPackage后才能使用,否则会报编译错误
ClassPool pool = ClassPool.getDefault();
CtClass ctClass = pool.get("com.maoxh.bytecode.javassist.other.Student");
pool.importPackage("java.util");
CtField make = CtField.make("private static Map myMap;", ctClass);
// 也可以使用全限定类名
// CtField make = CtField.make("private static java.util.Map myMap;", ctClass);
ctClass.addField(make);
4. CtMethod
4.1 新增方法
以下案例给Student类,新增一个add方法,返回两个int值的和。
一些说明
- CtMethod构造器提供了4个参数
- 参数1: 方法的返回类型
- 参数2: 方法名
- 参数3: 参数类型列表
- 参数4: 声明的类
- 方法体参数说明
- $1代表第一个参数,$2代表第二个参数。。。以此类推
- $0代表
this,如果是static方法 $0 不可用 - $$代表所有参数
- $args代表参数列表,是一个数组
- $w 代表包装类型。必须在转义表达式中用于类型转换。($w)将基础类型转换为对应的包装类型
- $r 代表着结果类型,必须在转换表达式中用作类型转换
- $cflow
- $_
- $sig 代表
java.lang.Class对象数组,代表正式的参数类型 - $class代表
java.lang.Class对象,代表当前操作的方法(等价$0的类型) - $type ,
java.lang.Class对象,代表正式的结果类型.
ClassPool pool = ClassPool.getDefault();
CtClass ctClass = pool.get("com.maoxh.bytecode.javassist.other.Student");
/**
* 创建一个方法
* 参数1:方法返回类型
* 参数2:方法名
* 参数3:参数列表
* 参数4:声明的类
*/
CtMethod add = new CtMethod(CtClass.intType, "add", new CtClass[]{CtClass.intType,CtClass.intType}, ctClass);
add.setModifiers(Modifier.PUBLIC);
add.setBody("{ return $1+$2;}");
ctClass.addMethod(add);
Class<?> aClass = ctClass.toClass();
Object o = aClass.newInstance();
Method addMethod = aClass.getDeclaredMethod("add", int.class, int.class);
Object res = addMethod.invoke(o, 33 , 66);
System.out.println(res);
也可以使用
CtMethod.make静态方法来创建method,以下案例展示了一个除法运算,同时增加了trycatch代码块,当异常时固定返回1。(需要注意的是,下面代码块注释了一行代码,这一行因为使用了可变参数而导致编译不通过。javassist不支持可变参数,如需要可用数组代替)
ClassPool pool = ClassPool.getDefault();
CtClass ctClass = pool.get("com.maoxh.bytecode.javassist.other.Student");
// CtMethod add = CtMethod.make("public Integer add(int ...a){int result = 0;" +
// "for(int k =0;k<a.length;k++){System.out.println($args[k]); result += a[k];} return ($r)result;" +
// "}", ctClass);
CtMethod add = CtMethod.make("public int division(int a,int b){int c = a / b; return c;}", ctClass);
add.setModifiers(Modifier.PUBLIC);
add.addCatch("{System.out.println($e); return 1;}", pool.get("java.lang.Exception"));
ctClass.addMethod(add);
4.2 修改方法
以下案例展示了如何修改已有方法,在方法开头打印了
参数,在方法结尾打印了结果,其中insertAfter第二个参数代表是否finally的。在下面的案例我们可以看到$_代表的是返回值,$args[1]等价$2
ClassPool pool = ClassPool.getDefault();
CtClass ctClass = pool.get("com.maoxh.bytecode.javassist.other.Student");
CtMethod multiply = ctClass.getDeclaredMethod("multiply");
multiply.insertBefore("{System.out.println($1);System.out.println($args[1]);}");
multiply.insertAfter("{System.out.println($_);}", true);
Class<?> aClass = ctClass.toClass();
Student o = (Student)aClass.newInstance();
o.multiply(4,5);
5. 拆箱和装箱
在Java中,装箱和拆箱操作是语法糖。对于字节码说来,是不存在装箱和拆箱的。所以Javassist的编译器不支持装箱拆箱操作。
Integer i = new Integer(1); // 在java和javassist中允许
Integer i = 1; // 在java允许 。javassist不允许
6. 冻结与解冻
当CtClass对象
writeFile(),toClass()或者toBytecode()转换成了类对象,Javassist将会冻结CtClass对象。此后任何对次对象的操作都是不允许的。之所以这样做,主要是因为此类已经被JVM加载,由于JVM本身不支持类的重复加载操作,所以不允许更改。
如果需要再次修改,需要调用
CtClass.defrost()
ClassPool pool = ClassPool.getDefault();
CtClass studentClass = pool.get("com.maoxh.bytecode.javassist.other.Student");
// studentClass.writeFile();
// Class<?> aClass = studentClass.toClass();
// byte[] bytes = studentClass.toBytecode();
/**
* 调用了defrost()方法之后,CtClass对象就可以随意修改了。
*/
studentClass.defrost();
CtField field1 = new CtField(CtClass.intType, "age1", studentClass);
studentClass.addField(field1);
7. 官方文档
8. 案例中使用到的Student类
public class Student {
private String name;
private Integer age;
private String address;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public int multiply(int a, int b){
int c= a * b;
return c;
}
}