javassist

javassist是一个开源的分析、编辑和创建Java字节码的类库。不需要了解虚拟机指令,就能动态生成类或者改变类的结构。

ClassPool

ClassPool是缓存CtClass对象的容器,所有的CtClass对象都在ClassPool中。所以,CtClass对象很多时,ClassPool会消耗很大的内存,为了避免内存的消耗,创建ClassPool对象时可以使用单例模式,或者对于CtClass对象,调用detach方法将其从ClassPool中移除。

创建ClassPool对象

构造函数1

public ClassPool()

创建一个根ClassPool对象

构造函数2

public ClassPool(boolean useDefaultPath)  

创建一个根ClassPool对象,当参数为true时,appendSystemPath将被调用。

构造函数3

public ClassPool(ClassPool parent)  

创建一个指定根的ClassPool对象,若无根,则参数为null。

单例

public static ClassPool getDefault()  

创建默认的ClassPool对象,该方法是单例的。当调用该方法时,等同于下面代码的调用。

ClassPool cp = new ClassPool(); cp.appendSystemPath();  

修改生成的类

首先生成一个类

public class ClassGeneratedByJavassist {  

    public static void main(String[] args) throws Exception {  

        ClassPool pool = ClassPool.getDefault();  

        CtClass ctClass = pool.makeClass("com.study.javassist.MyCC");  

        // 添加一个参数  
        CtField ctField = new CtField(CtClass.intType, "id", ctClass);  
        ctField.setModifiers(Modifier.PUBLIC);  
        ctClass.addField(ctField);  

        // 把生成的class文件写入文件  
        byte[] byteArr = ctClass.toBytecode();  
        FileOutputStream fos = new FileOutputStream(new File("D://MyCC.class"));  
        fos.write(byteArr);  
        fos.close();  
        System.out.println("over!!");  
    }  
}  

通过XJad反编译,结果如下:

package com.study.javassist;  


public class MyCC  
{  

    public int id;  

    public MyCC()  
    {  
    }  
}  

下面对MyClass.Java再添加一个name属性,如下:

public class ClassGeneratedByJavassist {  

 public static void main(String[] args) throws Exception {  

        ClassPool pool = ClassPool.getDefault();  

        CtClass ctClass = pool.makeClass("com.study.javassist.MyCC");  

        // 添加一个参数  
        CtField ctField = new CtField(CtClass.intType, "id", ctClass);  
        ctField.setModifiers(Modifier.PUBLIC);  
        ctClass.addField(ctField);  

        // 把生成的class文件写入文件  
        byte[] byteArr = ctClass.toBytecode();  
        FileOutputStream fos = new FileOutputStream(new File("D://MyCC.class"));  
        fos.write(byteArr);  
        fos.close();  
        System.out.println("over!!");  

        // 为了测试ctClass是否能够再修改,再添加一个域  
        CtField ctField2 = new CtField(pool.get("java.lang.String"), "name",  
                ctClass);  
        ctField2.setModifiers(Modifier.PUBLIC);  
        ctClass.addField(ctField2);  
        byteArr = ctClass.toBytecode();  
        fos = new FileOutputStream(new File("D://MyCC.class"));  
        fos.write(byteArr);  
        fos.close();  
        System.out.println(1111);  
    }  
}  

当执行时,抛出如下异常:

Exception in thread "main" java.lang.RuntimeException: com.study.javassist.MyCC class is frozen  
    at javassist.CtClassType.checkModify(CtClassType.java:286)  
    at javassist.CtField.setModifiers(CtField.java:239)  
    at com.study.javassist.ClassGeneratedByJavassist.main(ClassGeneratedByJavassist.java:36)  

原因解释如下
当CtClass对象通过writeFile()、toClass()、toBytecode()转化为Class后,Javassist冻结了CtClass对象,因此,JVM不允许再次加载Class文件,所以不允许对其修改。
因此,若想对CtClass对象进行修改,必须对其进行解冻,通过defrost()方法进行,如下所示:

public class ClassGeneratedByJavassist {  

    public static void main(String[] args) throws Exception {  

        ClassPool pool = ClassPool.getDefault();  

        CtClass ctClass = pool.makeClass("com.study.javassist.MyCC");  

        // 添加一个参数  
        CtField ctField = new CtField(CtClass.intType, "id", ctClass);  
        ctField.setModifiers(Modifier.PUBLIC);  
        ctClass.addField(ctField);  

        // 把生成的class文件写入文件  
        byte[] byteArr = ctClass.toBytecode();  
        FileOutputStream fos = new FileOutputStream(new File("D://MyCC.class"));  
        fos.write(byteArr);  
        fos.close();  
        System.out.println("over!!");  

        // 解冻CtClass对象  
        <span style="color:#ff0000;"><strong>ctClass.defrost();  
</strong></span>          
        // 为了测试ctClass是否能够再修改,再添加一个域  
        CtField ctField2 = new CtField(pool.get("java.lang.String"), "name",  
                ctClass);  
        ctField2.setModifiers(Modifier.PUBLIC);  
        ctClass.addField(ctField2);  
        byteArr = ctClass.toBytecode();  
        fos = new FileOutputStream(new File("D://MyCC.class"));  
        fos.write(byteArr);  
        fos.close();  
        System.out.println(1111);  
    }  
}  

通过反编译,结果如下:

package com.study.javassist;  


public class MyCC  
{  

    public int id;  
    public String name;  

    public MyCC()  
    {  
    }  
}  

类名操作

获取类名

ClassPool pool = ClassPool.getDefault();  
CtClass ctClass = pool.makeClass("com.study.javassist.MyCC");  
System.out.println(ctClass.getName());  

结果如下:

com.study.javassist.MyCC  

改变类名

ctClass.setName("com.study.javassist.MyCC2");  
System.out.println(ctClass.getName());  

结果:

com.study.javassist.MyCC2  

通过重命名冻结类定义新类

当CtClass对象通过writeFile( )或者toBytecode( )方法转为class文件后,javassist不允许对CtClass对象作后续修改,因此当通过调用setName修改类名时是不允许的。
可以通过ClassPool的getAndRename(oldName, newName)方法实现。
如下示例:

  ClassPool pool = ClassPool.getDefault();  
CtClass ct1 = pool.get("com.study.javassist.TestName");  
System.out.println(ct1.getName());  
ct1.writeFile();  
CtClass ct2 = pool.getAndRename("com.study.javassist.TestName", "com.study.javassist.TestName2");  
System.out.println(ct2.getName());  

结果如下:

com.study.javassist.TestName  
com.study.javassist.TestName2  

javassist、ASM 对比

  1. javassist是基于源码级别的API比基于字节码的ASM简单。
  2. 基于javassist开发,不需要了解字节码的一些知识,而且其封装的一些工具类可以简单实现一些高级功能。比如HotSwaper。
  3. ASM比javassist性能更快,灵活行也较高。
  4. javassist提供者动态代理接口最慢,比JDK自带的还慢

参考:
http://blog.csdn.net/yyywyr/article/details/16984335
http://blog.csdn.net/z69183787/article/details/39343259
http://wsmajunfeng.iteye.com/blog/1912983

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐