什么是Javassist
Javasisst是一个能让Java字节码操作变得简单的一个类库,用于在Java中编辑字节码;它使Java程序能够在运行时定义一个新类,并在JVM加载时修改类文件。与其他类似的字节码编辑器不同,Javassist提供了两个级别的API:源代码级和字节码级。如果用户使用源级API,他们可以在不知道Java字节码规范的情况下编辑类文件;整个API的设计只使用Java语言的词汇表您甚至可以以源文本的形式指定插入的字节码;另一方面,字节码级API允许用户像其他编辑器一样直接编辑类文件。
读取以及修改字节码
ClassPool pool = ClassPool.getDefault();
//step 1 : 创建一个类叫做 AnimalClass
CtClass cc = pool.makeClass("AnimalClass");
//step 2 : 新增一个name字段 类型String
CtField param = new CtField(pool.get("java.lang.String"),"name",cc);
param.setModifiers(Modifier.PRIVATE);
cc.addField(param,CtField.Initializer.constant("XiaoMao"));
// step 3 :新增get set方法
cc.addMethod(CtNewMethod.setter("setName",param));
cc.addMethod(CtNewMethod.getter("getName",param));
//step 4 :添加无参数的构造函数
CtConstructor constructor = new CtConstructor(new CtClass[]{},cc);
constructor.setBody("{name=\"xiaohei\";}");
cc.addConstructor(constructor);
//step 5:添加有参数构造函数
constructor = new CtConstructor(new CtClass[]{pool.get("java.lang.String")},cc);
constructor.setBody("{$0.name=$1;}");
cc.addConstructor(constructor);
CtMethod ctNewMethod = new CtMethod(CtClass.voidType,"printAnimalName",new CtClass[]{},cc);
ctNewMethod.setModifiers(Modifier.PUBLIC);
ctNewMethod.setBody("{System.out.println(name);}");
cc.addMethod(ctNewMethod);
cc.writeFile();
运行完上面的代码会生成如下一个类:
public class AnimalClass {
private String name = "XiaoMao";
public void setName(String var1) {
this.name = var1;
}
public String getName() {
return this.name;
}
public AnimalClass() {
this.name = "xiaohei";
}
public AnimalClass(String var1) {
this.name = var1;
}
public void printAnimalName() {
System.out.println(this.name);
}
}
ClassPool
是CtClass的对象容器,他的一个作用就是可以读取类文件来构造ctClass,并且保存起来使用,基于HashMap实现,默认的ClassPool使用与底层JVM相同的类路径,因此在某些情况下,可能需要向ClassPool添加类路径或类字节。
方法
- getDefault : 返回默认的
ClassPool
是单例模式的,一般通过该方法创建我们的ClassPool; - appendClassPath, insertClassPath : 将一个
ClassPath
加到类搜索路径的末尾位置 或 插入到起始位置。通常通过该方法写入额外的类搜索路径,以解决多个类加载器环境中找不到类的尴尬; - toClass : 将修改后的CtClass加载至当前线程的上下文类加载器中,CtClass的
toClass
方法是通过调用本方法实现。需要注意的是一旦调用该方法,则无法继续修改已经被加载的class; - get , getCtClass : 根据类路径名获取该类的CtClass对象,用于后续的编辑
CtClass
freeze 冻结一个类,不可修改;isFrozen:判断一个类是否冻结,writeFile:根据ctClass生成.class文件,prune:删除不必要的属性。
修改已经存在的class
public static void modifyMethod(){
ClassPool pool = ClassPool.getDefault();
try{
pool.appendClassPath("/Users/guosenlin/web/srs/");
CtClass ct = pool.get("AnimalClass");
CtMethod m = ct.getDeclaredMethod("printAnimalName");
m.insertAfter("{System.out.println(\"hshdjshdjshd\");}");
ct.writeFile();
}catch (Exception e){
e.printStackTrace();
}
}
上面的代码就是在已经存在的类文件AnimalClass里面的printAnimalName方法里插入一段函数:
public void printAnimalName() {
System.out.println(this.name);
Object var2 = null;
System.out.println("hshdjshdjshd");
}
Javassist实现在MainActivity插入代码
自定义Transform
class MyTransform extends Transform{
private Project project;
MyTransform(Project project1){
project = project1
}
@Override
void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {
TransformOutputProvider outputProvider = transformInvocation.getOutputProvider();
for (TransformInput input : transformInvocation.getInputs()) {
}
拿到编译时期的类文件
input.directoryInputs.each {
DirectoryInput directoryInput ->
println ("src.absolutePath:" + directoryInput.file.absolutePath)
MyInject.inject(directoryInput.file.absolutePath,project)
def dest = outputProvider.getContentLocation(directoryInput.name,
directoryInput.contentTypes, directoryInput.scopes, Format.DIRECTORY)
//将 input 的目录复制到 output 指定目录
FileUtils.copyDirectory(directoryInput.file, dest)
}
注入代码
final static inject(String path, Project project){
println("android.jar: "+project.android.bootClasspath[0])
/**
* 将当前路径加入类池
*/
pool.appendClassPath(path);
//android.jar
pool.appendClassPath(project.android.bootClasspath[0].toString());
pool.importPackage("android.os.Bundle")
File dir = new File(path)
if (dir.isDirectory()){
dir.eachFileRecurse{ file ->
String filePath = file.absolutePath
print("filepath: "+ filePath)
if (file.getName().equals("MainActivity.class")){
print("filepath: find the MainActivity ")
//获取Class
CtClass mainCtClass = pool.get("com.guosen.jassistdemo.MainActivity")
if (mainCtClass.isFrozen()){
mainCtClass.defrost()
}
CtMethod method = mainCtClass.getDeclaredMethod("onCreate")
String insertstr = "{android.widget.Toast.makeText(this,\"这个吐司我是偷偷加进去的\",android.widget.Toast.LENGTH_SHORT).show();}";
method.insertAfter(insertstr)
mainCtClass.writeFile(path)
mainCtClass.detach()
// android.widget.Toast.makeText(this,"sdsdsd",android.widget.Toast.LENGTH_SHORT).show();
}
}
}
}
注册Transfrom
class MyJavassistPlugin implements Plugin<Project> {
@Override
void apply(Project project) {
print("《应用自定义插件guosen》")
//注册Transfrom
project.getExtensions().findByType(AppExtension.class)
.registerTransform(new MyTransform(project))
}
}
效果:(运行项目或者执行build)
有些热修复就是采用这种插入代码技术如QQ补丁 www.jianshu.com/p/62503c6db…