Javassist 字节码增强技术 使用全解析
Java中所有的类都被编译为class文件来运行,在编译完class文件之后,类不能再被显示修改,而Javassist
就是用来处理编译后的class文件,它可以用来修改方法或者新增方法,并且不需要深入了解字节码,还可以生成一个新的类对象。
创建class
创建maven项目,引入Javassist库 po
<!-- https://mvnrepository.com/artifact/javassist/javassist -->
<dependency>
<groupId>javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.12.1.GA</version>
</dependency>
使用javassist来创建一个CodeClass类
package com.mscloudemsh.zookeeper.test;
import javassist.*;
import java.io.IOException;
public class CreateCodeClass {
public static void main(String[] args) throws CannotCompileException, IOException, NotFoundException {
ClassPool pool=ClassPool.getDefault();
//创建一个空类
CtClass cc=pool.makeClass("com.mscloudemsh.zookeeper.test.Person");
//2 新增一个字段
CtField param=new CtField(pool.get("java.lang.String"),"name",cc);
//访问级private
param.setModifiers(Modifier.PRIVATE);
cc.addField(param);
//3生成getter\setter方法
cc.addMethod(CtNewMethod.setter("setName",param));
cc.addMethod(CtNewMethod.getter("getName",param));
// 4. 添加无参的构造函数
CtConstructor cons = new CtConstructor(new CtClass[]{}, cc);
cons.setBody("{name = \"xiaohong\";}");
cc.addConstructor(cons);
// 5. 添加有参的构造函数
cons = new CtConstructor(new CtClass[]{pool.get("java.lang.String")}, cc);
// $0=this / $1,$2,$3... 代表方法参数
cons.setBody("{$0.name = $1;}");
cc.addConstructor(cons);
CtMethod ctMethod=new CtMethod(CtClass.voidType,"printName",new CtClass[]{},cc);
ctMethod.setModifiers(Modifier.PUBLIC);
ctMethod.setBody("{System.out.println(name);}");
cc.addMethod(ctMethod);
cc.writeFile("/Users/kevin/Downloads/zookeeper-source-desc/src/main/java");
}
}
执行完之后生成了CodeClass.class
使用方法
从上文的demo中可以看到部分使用方法,在javassist中CtClass代表的就是类class,ClassPool就是CtClass的容器,ClassPool维护了所有创建的CtClass对象,需要注意的是当CtClass数量过大会占用大量内存,需要调用CtClass.detach()释放内存。
ClassPool重点有以下几个方法:
1. getDefault() 单例获取ClassPool
2. appendClassPath() 将目录添加到ClassPath
3. insertClassPath() 在ClassPath插入jar
4. get() 根据名称获取CtClass对象
5. toClass() 将CtClass转为Class 一旦被转换则不能修改
6. makeClass() 创建新的类或接口
更多移步官方文档:http://www.javassist.org/html/javassist/ClassPool.html
CtClass需要关注的方法:
1. addConstructor() 添加构造函数
2. addField() 添加字段
3. addInterface() 添加接口
4. addMethod() 添加方法
5. freeze() 冻结类使其不能被修改
6. defrost() 解冻使其能被修改
7. detach() 从ClassPool中删除类
8. toBytecode() 转字节码
9. toClass() 转Class对象
10. writeFile() 写入.class文件
11. setModifiers() 设置修饰符
移步:http://www.javassist.org/html/javassist/CtClass.html
CtMethod继承CtBehavior,需要关注的方法:
1. insertBefore 在方法的起始位置插入代码
2. insterAfter 在方法的所有 return 语句前插入代码
3. insertAt 在指定的位置插入代码
4. setBody 将方法的内容设置为要写入的代码,当方法被 abstract修饰时,该修饰符被移除
5. make 创建一个新的方法
更多移步:http://www.javassist.org/html/javassist/CtBehavior.html
在setBody()中我们使用了$
符号代表参数
// $0代表this $1代表第一个传入的参数 类推
printName.setBody("{System.out.println($0.name);}");
使用CtClass生成对象
上文我们生成了一个ctClass对象对应的是CodeClass.class,怎么调用CodeClass类生成对象、调用属性或方法?
采用一种常用的方式
通过接口调用
新建一个接口CodeClassI,将CodeClass类的方法全部抽象出来
package com.mscloudemsh.zookeeper.test;
import com.mscloudmesh.log.CodeClassI;
import javassist.*;
public class InvokerDemo {
public static void main(String[] args) throws NotFoundException, CannotCompileException, IllegalAccessException, InstantiationException {
ClassPool pool = ClassPool.getDefault();
pool.appendClassPath
("/Users/kevin/Downloads/zookeeper-source-desc/src/main/java/com/mscloudmesh/log");
//获取接口
CtClass codeClassI=pool.get("com.mscloudmesh.log.CodeClassI");
//获取上面生成的类
CtClass ctClass=pool.get("com.mscloudmesh.log.CodeClass");
ctClass.setInterfaces(new CtClass[]{codeClassI});
//在execute方法前后加入自定义的逻辑
CtMethod executeMethod = ctClass.getDeclaredMethod("execute");
executeMethod.insertBefore("System.out.println(\"--开始\");");
executeMethod.insertAfter( "System.out.println(\"--结束\");");
//通过接口直接调用
CodeClassI classI= (CodeClassI) ctClass.toClass().newInstance();
System.out.println(classI.getName());
classI.setName("mr kevin");
classI.execute();
}
}