ClassPool CtClass浅析

最近在看android中的热更新原理,里面有用到javassist来更改.class,因而又恶补了下ClassPool和CtClass的相关使用。虽然android中现在热更新是用 groovy groovy和java语法很类似,所以先弄java版的~

什么是javassist

Javassit是一个处理Java字节码的类库。Java字节码存储在名叫class file的二进制文件里。每个class文件包含一个Java类或者接口。Javassit.CtClass是一个class文件的抽象表示。一个CtClass(compile-time class)对象可以用来处理一个class文件。

通过javassist生成.class文件

public static void main(String[] args) {

        //默认的类搜索路径
        ClassPool pool = ClassPool.getDefault();
        //获取一个ctClass对象
        CtClass ctClass = pool.makeClass("com.luoxiaohui.Test");
        try {
            //添加age属性
            ctClass.addField(CtField.make("private int age;", ctClass));
            //添加setAge方法
            ctClass.addMethod(CtMethod.make("public void setAge(int age){this.age = age;}", ctClass));
            //添加getAge方法
            ctClass.addMethod(CtMethod.make("public int getAge(){return this.age;}", ctClass));
            //将ctClass生成字节数组,并写入文件
            byte[] byteArray = ctClass.toBytecode();
            FileOutputStream output = new FileOutputStream("/Users/luoxiaohui/Desktop/test/Test.class");
            output.write(byteArray);                                        
            output.close();
            System.out.println("文件写入成功!!!");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

可以看到相应目录下生成了Test.class文件,然后通过JD-GUI工具打开,如图所示: 
这里写图片描述 
可以看到,属性和两个方法,都已经写入到.class文件中,OK啦!

如何修改已经被JVM加载的.class文件

模拟被JVM加载的.class文件代码

ClassPool pool = ClassPool.getDefault();
CtClass ctClass = pool.makeClass("com.luoxiaohui.Test");
    try {
        //添加属性
        ctClass.addField(CtField.make("private int age;", ctClass));
        //添加setAge方法
        ctClass.addMethod(CtMethod.make("public void setAge(int age){this.age = age;}", ctClass));
        ctClass.addMethod(CtMethod.make("public int getAge(){return this.age;}", ctClass));

        byte[] byteArray = ctClass.toBytecode();
        FileOutputStream output = new FileOutputStream("/Users/luoxiaohui/Desktop/test/Test.class");
        output.write(byteArray);                                        
        output.close();
        System.out.println("文件生成成功!!!");

        //这里用pool.get()去获取ctClass对象,表示默认JVM已经加载此类.
        ctClass = pool.get("com.luoxiaohui.Test");
        ctClass.addField(CtField.make("private String sex;", ctClass));
        ctClass.addField(CtField.make("private String name;", ctClass));

        byteArray = ctClass.toBytecode();
        output = new FileOutputStream("/Users/luoxiaohui/Desktop/test/Test.class");
        output.write(byteArray);
        output.close();

        System.out.println("文件修改成功!!!!");

    } catch (Exception e) {
        e.printStackTrace();
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

发现报错,log如下所示: 
这里写图片描述 
报错位置在

ctClass.addField(CtField.make("private String sex;", ctClass));
  • 1

冻结class原因

如果一个CtClass对象通过writeFile(),toClass()或者toBytecode()转换成了class文件,那么Javassist会冻结这个CtClass对象。后面就不能继续修改这个CtClass对象了。这样是为了警告开发者不要修改已经被JVM加载的class文件,因为JVM不允许重新加载一个类。

然后我在调用pool.get()之前,先调用代码:

if(ctClass.isFrozen()){
    ctClass.defrost();
}
  • 1
  • 2
  • 3

运行代码,结果还是会报错,log如图所示: 
这里写图片描述

被精简原因

如果ClassPool.doPruning被设置成true,那么Javassist会在冻结一个对象的时候对这个对象进行精简。为了减少ClassPool的内存占用,精简的时候会丢弃class中不需要的属性。例如Code_attribute结构(即是方法体)会被丢弃。因此,如果一个CtClass对象被精简了,那么方法的字节码是不能访问的,留下的只有方法名,方法的签名和annotation。被精简的CtClass对象不能够再被defrost。ClassPool.doPruning的默认值是true。 
所以,如果要阻止对某一个特定的CtClass对象的精简,即需要修改某个.class文件,需要在这个CtClass对象上先调用stopPruing()方法:

ctClass.stopPruning(true);
  • 1

完整代码

完整代码如下所示:

ClassPool pool = ClassPool.getDefault();
CtClass ctClass = pool.makeClass("com.luoxiaohui.Test");
ctClass.stopPruning(true);
    try {
        //添加属性
        ctClass.addField(CtField.make("private int age;", ctClass));
        //添加setAge方法
        ctClass.addMethod(CtMethod.make("public void setAge(int age){this.age = age;}", ctClass));
        ctClass.addMethod(CtMethod.make("public int getAge(){return this.age;}", ctClass));

        byte[] byteArray = ctClass.toBytecode();
        FileOutputStream output = new FileOutputStream("/Users/luoxiaohui/Desktop/test/Test.class");
        output.write(byteArray);                                        
        output.close();
        System.out.println("文件写入成功!!!");

        if(ctClass.isFrozen()){
            ctClass.defrost();
        }
        ctClass = pool.get("com.luoxiaohui.Test");
        ctClass.addField(CtField.make("private String sex;", ctClass));
        ctClass.addField(CtField.make("private String name;", ctClass));

        byteArray = ctClass.toBytecode();
        output = new FileOutputStream("/Users/luoxiaohui/Desktop/test/Test.class");
        output.write(byteArray);
        output.close();

        System.out.println("文件修改成功!!!!");

    } catch (Exception e) {
        e.printStackTrace();
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

参考博客:http://blog.chinaunix.net/uid-21718047-id-3342374.html


from: https://blog.csdn.net/a394268045/article/details/51996082

猜你喜欢

转载自blog.csdn.net/garfielder007/article/details/80501858